优化flowable接口

This commit is contained in:
2025-12-01 10:47:48 +08:00
parent 789bd1b926
commit a2796f7bee
15 changed files with 255 additions and 117 deletions

View File

@@ -0,0 +1,76 @@
{
"process": {
"id": "simplified_process_001",
"name": "简化版-串行流程:本地应用+HPC+导出报告"
},
"flowElements": [
{
"id": "start",
"type": "startEvent",
"name": "开始",
"outgoingFlows": ["flow1"]
},
{
"id": "task_localApp",
"type": "userTask",
"name": "本地应用操作",
"incomingFlows": ["flow1"],
"outgoingFlows": ["flow2"]
},
{
"id": "task_HPC",
"type": "serviceTask",
"name": "执行HPC任务",
"incomingFlows": ["flow2"],
"outgoingFlows": ["flow3"],
"extensionElements": {
"executeConfig": {
"executeType": "HPC",
"asyncCallback": true
}
}
},
{
"id": "task_exportReport",
"type": "serviceTask",
"name": "生成报告脚本",
"incomingFlows": ["flow3"],
"outgoingFlows": ["flow4"],
"extensionElements": {
"executeConfig": {
"executeType": "exportWordScript"
}
}
},
{
"id": "end",
"type": "endEvent",
"name": "结束",
"incomingFlows": ["flow4"]
},
{
"id": "flow1",
"type": "sequenceFlow",
"sourceRef": "start",
"targetRef": "task_localApp"
},
{
"id": "flow2",
"type": "sequenceFlow",
"sourceRef": "task_localApp",
"targetRef": "task_HPC"
},
{
"id": "flow3",
"type": "sequenceFlow",
"sourceRef": "task_HPC",
"targetRef": "task_exportReport"
},
{
"id": "flow4",
"type": "sequenceFlow",
"sourceRef": "task_exportReport",
"targetRef": "end"
}
]
}

View File

@@ -1,6 +1,7 @@
CREATE TABLE `flowable`.`process_node_param`
(
`id` bigint NOT NULL AUTO_INCREMENT,
`processDefinitionId` varchar(64) DEFAULT NULL COMMENT '流程定义ID',
`processInstanceId` varchar(64) DEFAULT NULL COMMENT '流程实例ID',
`nodeId` varchar(64) DEFAULT NULL COMMENT '节点ID',
`paramJson` text COMMENT '输入参数JSON',

View File

@@ -3,15 +3,16 @@ package com.sdm.flowable.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sdm.common.common.SdmResponse;
import com.sdm.common.feign.inter.flowable.IFlowableFeignClient;
import com.sdm.flowable.delegate.UniversalDelegate;
import com.sdm.flowable.delegate.handler.HpcHandler;
import com.sdm.flowable.dto.ProcessDefinitionDTO;
import com.sdm.flowable.dto.req.AsyncCallbackRequest;
import com.sdm.flowable.dto.req.CompleteTaskReq;
import com.sdm.flowable.dto.req.RetryRequest;
import com.sdm.common.entity.resp.flowable.ProcessInstanceResp;
import com.sdm.flowable.dto.resp.DeployFlowableResp;
import com.sdm.flowable.process.ProcessService;
import com.sdm.flowable.service.IProcessNodeParamService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
@@ -23,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/process")
public class ProcessController implements IFlowableFeignClient {
@@ -68,20 +70,13 @@ public class ProcessController implements IFlowableFeignClient {
// 部署流程
@PostMapping("/deploy")
public Map<String, Object> deploy(@RequestBody ProcessDefinitionDTO processDTO) {
Map<String, Object> result = new HashMap<>();
public SdmResponse<DeployFlowableResp> deploy(@RequestBody ProcessDefinitionDTO processDTO) {
try {
Deployment deployment = processService.deploy(processDTO);
result.put("success", true);
result.put("deploymentId", deployment.getId());
result.put("deploymentName", deployment.getName());
return processService.deploy(processDTO);
} catch (Exception e) {
result.put("success", false);
result.put("message", "部署失败: " + e.getMessage());
log.error("流程部署失败: ", e);
return SdmResponse.failed("流程部署失败");
}
return result;
}
/**
@@ -108,28 +103,14 @@ public class ProcessController implements IFlowableFeignClient {
/**
* 获取部署的流程定义元数据信息
* 1、查询所有流程定义所有版本
* 2、查询所有流程的"最新版本"
* 3、查询某个流程的所有版本
* 4、查询某个流程的最新版本
* 5、查询版本从大到小
*
* @param processDefinitionKey
* @param latest
* @param order
* @param page
* @param size
* @param processDefinitionId
* @return
*/
@GetMapping("/listProcessDefinitionsMetaInfo")
public Map<String, Object> listProcessDefinitionsMetaInfo(
@RequestParam(required = false) String processDefinitionKey,
@RequestParam(defaultValue = "false") boolean latest,
@RequestParam(defaultValue = "asc") String order,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
public SdmResponse listProcessDefinitionsMetaInfo(@RequestParam String processDefinitionId) {
return processService.listProcessDefinitionsMetaInfo(processDefinitionKey, latest, order, page, size);
return processService.listProcessDefinitionsMetaInfo(processDefinitionId);
}
/**
@@ -160,11 +141,17 @@ public class ProcessController implements IFlowableFeignClient {
}
// 保存节点用户输入参数基于流程定义ID作为参数模板
// 保存节点用户输入参数(基于流程定义ID等流程启动后保存流程实例ID作为参数模板)
@PostMapping("/saveParamsByDefinitionId")
public void saveParamsByDefinitionId(@RequestParam String processDefinitionId, @RequestParam String nodeId,
@RequestBody Map<String, Object> params) {
processNodeParamService.saveParamByDefinitionId(processDefinitionId, nodeId, params);
processNodeParamService.saveParamByProcessDefinitionId(processDefinitionId, nodeId, params);
}
// 启动流程实例后更新流程参数的流程实例id
@PostMapping("/updateNodeParamProcessInstanceId")
public void updateNodeParamProcessInstanceId(@RequestParam String processDefinitionId, @RequestParam String processInstanceId) {
processNodeParamService.updateNodeParamProcessInstanceId(processInstanceId, processDefinitionId);
}
// 启动流程实例
@@ -236,7 +223,7 @@ public class ProcessController implements IFlowableFeignClient {
}
/**
* 流程节点继续执行(完成人工节点/或者等待用户输入后继续手动执行的节点)
* 流程节点继续执行(完成人工节点/或者等待用户输入后继续手动执行的节点)
*
* @param req
* @return
@@ -260,7 +247,7 @@ public class ProcessController implements IFlowableFeignClient {
/**
* 用户点击"重试"按钮传入目标节点ID
*
* @param request 重试请求参数包括流程实例ID、目标节点ID和新变量
* @param request 重试请求参数包括流程实例ID、目标节点ID和新变量
* @return 重试结果
*/
@PostMapping("/retryToNode")

View File

@@ -0,0 +1,11 @@
package com.sdm.flowable.dto.resp;
import lombok.Data;
@Data
public class DeployFlowableResp {
String deploymentId;
String deploymentName;
String processDefinitionId;
String processDefinitionKey;
}

View File

@@ -32,6 +32,10 @@ public class ProcessNodeParam implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "流程定义ID")
@TableField("processDefinitionId")
private String processDefinitionId;
@ApiModelProperty(value = "流程实例ID")
@TableField("processInstanceId")
private String processInstanceId;

View File

@@ -1,11 +1,13 @@
package com.sdm.flowable.process;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.sdm.common.common.SdmResponse;
import com.sdm.flowable.constants.FlowableConfig;
import com.sdm.flowable.delegate.UniversalDelegate;
import com.sdm.flowable.dto.ProcessDefinitionDTO;
import com.sdm.flowable.dto.req.AsyncCallbackRequest;
import com.sdm.flowable.dto.req.RetryRequest;
import com.sdm.flowable.dto.resp.DeployFlowableResp;
import com.sdm.flowable.enums.FlowElementTypeEnums;
import com.sdm.flowable.util.Dto2BpmnConverter;
import com.sdm.flowable.constants.FlowableConfig;
@@ -58,7 +60,7 @@ public class ProcessService {
private UniversalDelegate universalDelegate;
// 部署流程前端传入Flowable标准JSON
public Deployment deploy(ProcessDefinitionDTO processDTO) throws Exception {
public SdmResponse<DeployFlowableResp> deploy(ProcessDefinitionDTO processDTO) throws Exception {
log.info("开始部署流程定义");
BpmnModel bpmnModel = dto2BpmnConverter.convert(processDTO);
log.info("BPMN模型转换完成");
@@ -66,7 +68,7 @@ public class ProcessService {
// 检查BPMN模型是否有效
if (bpmnModel.getProcesses().isEmpty()) {
log.error("无效的BPMN模型未找到任何流程定义");
throw new RuntimeException("无效的BPMN模型未找到任何流程定义");
return SdmResponse.failed("无效的BPMN模型未找到任何流程定义");
}
// 验证BPMN模型
@@ -78,7 +80,7 @@ public class ProcessService {
errorMsg.append("\n - ").append(error.toString());
}
log.error("BPMN模型验证失败: {}", errorMsg.toString());
throw new RuntimeException(errorMsg.toString());
return SdmResponse.failed("BPMN模型验证失败");
}
Deployment deployment = repositoryService.createDeployment()
@@ -87,7 +89,20 @@ public class ProcessService {
.deploy();
log.info("流程部署成功, 部署ID: {}, 部署名称: {}", deployment.getId(), deployment.getName());
return deployment;
// 查询该部署下的流程定义(假设只部署了一个流程)
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.singleResult();
if (processDefinition == null) {
return SdmResponse.failed("部署成功,但未找到关联的流程定义");
}
DeployFlowableResp resp = new DeployFlowableResp();
resp.setDeploymentId(deployment.getId());
resp.setDeploymentName(deployment.getName());
resp.setProcessDefinitionId(processDefinition.getId());
resp.setProcessDefinitionKey(processDefinition.getKey());
return SdmResponse.success(resp);
}
public List<Map<String, Object>> listAllDeployments() {
@@ -130,69 +145,9 @@ public class ProcessService {
.collect(Collectors.toList());
}
public Map<String, Object> listProcessDefinitionsMetaInfo(
String processDefinitionKey,
boolean latest,
String order,
int page,
int size) {
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
// 可选流程 key
if (processDefinitionKey != null && !processDefinitionKey.isEmpty()) {
query.processDefinitionKey(processDefinitionKey);
}
// 是否只查最新版本
if (latest) {
query.latestVersion();
}
// 排序
if ("desc".equalsIgnoreCase(order)) {
query.orderByProcessDefinitionVersion().desc();
} else {
query.orderByProcessDefinitionVersion().asc();
}
// 总记录数
long total = query.count();
// 分页查询
List<ProcessDefinition> list = query.listPage(page * size, size);
// 转换成 DTO
List<Map<String, Object>> data = list.stream()
.map(pd -> {
Map<String, Object> info = new HashMap<>();
info.put("id", pd.getId());
info.put("key", pd.getKey());
info.put("name", pd.getName());
info.put("version", pd.getVersion());
info.put("deploymentId", pd.getDeploymentId());
info.put("resourceName", pd.getResourceName());
info.put("diagramResourceName", pd.getDiagramResourceName());
info.put("suspended", pd.isSuspended());
// 获取部署时间
Deployment dep = repositoryService.createDeploymentQuery()
.deploymentId(pd.getDeploymentId())
.singleResult();
info.put("deploymentTime", dep != null ? dep.getDeploymentTime() : null);
return info;
})
.collect(Collectors.toList());
// 返回分页信息
Map<String, Object> result = new HashMap<>();
result.put("total", total);
result.put("page", page);
result.put("size", size);
result.put("data", data);
return result;
public SdmResponse listProcessDefinitionsMetaInfo(String processDefinitionId) {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
return SdmResponse.success(processDefinition);
}
public List<Map<String, Object>> listNodesByProcessDefinitionKey(
@@ -342,12 +297,12 @@ public class ProcessService {
}
// 获取BPMN模型用于调试和验证
public BpmnModel getBpmnModel(ProcessDefinitionDTO processDTO) throws JsonProcessingException {
public BpmnModel getBpmnModel(ProcessDefinitionDTO processDTO) throws Exception {
return dto2BpmnConverter.convert(processDTO);
}
// 验证并返回模型验证错误信息
public List<ValidationError> validateModel(ProcessDefinitionDTO processDTO) throws JsonProcessingException {
public List<ValidationError> validateModel(ProcessDefinitionDTO processDTO) throws Exception {
BpmnModel bpmnModel = dto2BpmnConverter.convert(processDTO);
ProcessValidator validator = new ProcessValidatorFactory().createDefaultProcessValidator();
return validator.validate(bpmnModel);

View File

@@ -14,6 +14,7 @@ import java.util.Map;
* @since 2025-11-25
*/
public interface IProcessNodeParamService extends IService<ProcessNodeParam> {
void saveParamByDefinitionId(String processDefinitionId, String nodeId, Map<String, Object> params);
void saveParamByProcessDefinitionId(String processDefinitionId, String nodeId, Map<String, Object> params);
void updateNodeParamProcessInstanceId(String processInstanceId, String processDefinitionId);
Map<String, Object> getParam(String procInstId, String nodeId);
}

View File

@@ -12,7 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -34,9 +33,9 @@ public class ProcessNodeParamServiceImpl extends ServiceImpl<ProcessNodeParamMap
private RuntimeService runtimeService;
// 保存节点输入参数(按流程实例保存,用于参数模板)
public void saveParamByDefinitionId(String processInstanceId, String nodeId, Map<String, Object> params) {
public void saveParamByProcessDefinitionId(String processDefinitionId, String nodeId, Map<String, Object> params) {
ProcessNodeParam param = new ProcessNodeParam();
param.setProcessInstanceId(processInstanceId);
param.setProcessDefinitionId(processDefinitionId);
param.setNodeId(nodeId);
try {
param.setParamJson(objectMapper.writeValueAsString(params));
@@ -44,7 +43,7 @@ public class ProcessNodeParamServiceImpl extends ServiceImpl<ProcessNodeParamMap
throw new RuntimeException("参数序列化失败", e);
}
// 存在则更新,不存在则插入
ProcessNodeParam existing = this.lambdaQuery().eq(ProcessNodeParam::getProcessInstanceId, processInstanceId).eq(ProcessNodeParam::getNodeId, nodeId).one();
ProcessNodeParam existing = this.lambdaQuery().eq(ProcessNodeParam::getProcessDefinitionId, processDefinitionId).eq(ProcessNodeParam::getNodeId, nodeId).one();
if (existing != null) {
param.setId(existing.getId());
this.updateById(param);
@@ -53,6 +52,16 @@ public class ProcessNodeParamServiceImpl extends ServiceImpl<ProcessNodeParamMap
}
}
// 流程启动后更新流程实例ID
public void updateNodeParamProcessInstanceId(String processInstanceId, String processDefinitionId) {
ProcessNodeParam param = this.lambdaQuery().eq(ProcessNodeParam::getProcessDefinitionId, processDefinitionId).one();
if (param != null) {
param.setProcessInstanceId(processInstanceId);
this.updateById(param);
}
}
// 查询节点输入参数(流程执行时调用)
public Map<String, Object> getParam(String procInstId, String nodeId) {
ProcessNodeParam param = this.lambdaQuery().eq(ProcessNodeParam::getProcessInstanceId, procInstId)

View File

@@ -387,16 +387,18 @@ public class Dto2BpmnConverter {
userTask.setName(nodeDto.getName());
// 绑定控制参数(和 ServiceTask 类似)
BaseExecuteConfig userTaskExecuteConfig = nodeDto.getExtensionElements().getExecuteConfig();
// 设置异步回调节点ID
userTaskExecuteConfig.setCallbackNodeId(asyncTaskMap.getOrDefault(nodeDto.getId(), null));
if (userTaskExecuteConfig != null) {
if (nodeDto.getExtensionElements() != null && nodeDto.getExtensionElements().getExecuteConfig() != null) {
BaseExecuteConfig userTaskExecuteConfig = nodeDto.getExtensionElements().getExecuteConfig();
// 设置异步回调节点ID
userTaskExecuteConfig.setCallbackNodeId(asyncTaskMap.getOrDefault(nodeDto.getId(), null));
String configJson = objectMapper.writeValueAsString(userTaskExecuteConfig);
ExtensionElement extensionElement = createFlowableElement(
FlowableConfig.EXECUTECONFIG, configJson);
userTask.getExtensionElements()
.computeIfAbsent(FlowableConfig.EXECUTECONFIG, k -> new ArrayList<>())
.add(extensionElement);
}
// 设置用户任务的属性,使其可以被任何人处理
// 不设置 assignee 或 candidateUsers这样任何人都可以处理任务
@@ -416,15 +418,17 @@ public class Dto2BpmnConverter {
serviceTask.setImplementation("${universalDelegate}");
serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
// 添加 Flowable 扩展属性
BaseExecuteConfig serviceTaskExecuteConfig = nodeDto.getExtensionElements().getExecuteConfig();
// 设置异步回调节点ID
serviceTaskExecuteConfig.setCallbackNodeId(asyncTaskMap.getOrDefault(nodeDto.getId(), null));
if (serviceTaskExecuteConfig != null) {
if (nodeDto.getExtensionElements() != null && nodeDto.getExtensionElements().getExecuteConfig() != null) {
// 添加 Flowable 扩展属性
BaseExecuteConfig serviceTaskExecuteConfig = nodeDto.getExtensionElements().getExecuteConfig();
// 设置异步回调节点ID
serviceTaskExecuteConfig.setCallbackNodeId(asyncTaskMap.getOrDefault(nodeDto.getId(), null));
String configJson = objectMapper.writeValueAsString(serviceTaskExecuteConfig);
ExtensionElement extensionElement = createFlowableElement(FlowableConfig.EXECUTECONFIG, configJson);
serviceTask.getExtensionElements().computeIfAbsent(FlowableConfig.EXECUTECONFIG, k -> new ArrayList<>())
.add(extensionElement);
}
process.addFlowElement(serviceTask);

View File

View File

@@ -0,0 +1,25 @@
#!/bin/bash
# Spring Boot 项目重启脚本
# 定义基础路径(公共参数)
BASE_DIR="/home/app/flowable"
echo "=== 开始重启项目 ==="
# 先停止服务
if [ -f "${BASE_DIR}/stop.sh" ]; then
"${BASE_DIR}/stop.sh"
else
echo "错误:未找到停止脚本 ${BASE_DIR}/stop.sh"
exit 1
fi
# 再启动服务
if [ -f "${BASE_DIR}/start.sh" ]; then
"${BASE_DIR}/start.sh"
else
echo "错误:未找到启动脚本 ${BASE_DIR}/start.sh"
exit 1
fi
echo "=== 重启操作完成 ==="

View File

@@ -0,0 +1,45 @@
#!/bin/bash
# Spring Boot 项目启动脚本
JAR_PATH="/home/app/flowable"
JAR_NAME="flowable-0.0.1-SNAPSHOT.jar"
FULL_JAR_PATH="${JAR_PATH}/${JAR_NAME}"
# 与logback.xml保持一致的日志路径
LOG_HOME="/home/app/flowable/logs"
LOG_FILE="${LOG_HOME}/running.log"
# JVM参数
JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_HOME}/heapdump.hprof"
# 函数定义
check_jar_exists() {
if [ ! -f "${FULL_JAR_PATH}" ]; then
echo "ERROR: Jar包不存在路径${FULL_JAR_PATH}"
exit 1
fi
}
get_running_pid() {
ps -ef | grep "${JAR_NAME}" | grep -v "grep" | awk '{print $2}'
}
# 检查是否已运行
PID=$(get_running_pid)
if [ -n "${PID}" ]; then
echo "项目已在运行中PID: ${PID}"
exit 0
fi
# 检查Jar包是否存在
check_jar_exists
# 确保日志目录存在
if [ ! -d "${LOG_HOME}" ]; then
mkdir -p "${LOG_HOME}"
echo "日志目录不存在,已自动创建:${LOG_HOME}"
fi
# 启动项目
echo "正在启动项目..."
nohup java ${JVM_OPTS} -Dspring.profiles.active=dev -jar "${FULL_JAR_PATH}" > "${LOG_FILE}" 2>&1 &

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Spring Boot 项目状态查询脚本
JAR_NAME="flowable-0.0.1-SNAPSHOT.jar"
LOG_HOME="/home/app/flowable/logs"
LOG_FILE="${LOG_HOME}/running.log"
# 函数定义
get_running_pid() {
ps -ef | grep "${JAR_NAME}" | grep -v "grep" | awk '{print $2}'
}
# 查看服务状态
PID=$(get_running_pid)
if [ -n "${PID}" ]; then
echo "项目运行中PID: ${PID}"
echo "日志文件路径:${LOG_FILE}"
else
echo "项目未在运行中"
fi

View File