From 05a2affb2f46545c5122a1a426a4a5bb99d9bfeb Mon Sep 17 00:00:00 2001 From: gulongcheng <474084054@qq.com> Date: Thu, 27 Nov 2025 12:36:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=BC=95=E6=93=8E=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E6=90=AD=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/compiler.xml | 2 +- .../impl/MinioFileIDataFileServiceImpl.java | 4 +- flowable/.gitattributes | 2 + flowable/.gitignore | 33 ++ .../.mvn/wrapper/maven-wrapper.properties | 3 + flowable/mvnw | 295 ++++++++++ flowable/mvnw.cmd | 189 +++++++ flowable/pom.xml | 92 ++++ flowable/sql/async_task_record.sql | 19 + flowable/sql/process_node_param.sql | 12 + .../com/sdm/flowable/FlowableApplication.java | 21 + .../executeConfig/BaseExecuteConfig.java | 35 ++ .../executeConfig/CloudAppExecuteConfig.java | 4 + .../DataProcessExecuteConfig.java | 11 + .../executeConfig/DefaultExecuteConfig.java | 11 + .../executeConfig/HPCExecuteConfig.java | 4 + .../executeConfig/HttpExecuteConfig.java | 15 + .../executeConfig/LocalAppExecuteConfig.java | 4 + .../flowable/constants/FlowableConfig.java | 8 + .../controller/ProcessController.java | 253 +++++++++ .../flowable/dao/AsyncTaskRecordMapper.java | 16 + .../flowable/dao/ProcessNodeParamMapper.java | 16 + .../flowable/delegate/UniversalDelegate.java | 94 ++++ .../delegate/handler/CloudAppHandler.java | 16 + .../delegate/handler/DataProcessHandler.java | 16 + .../delegate/handler/ExecutionHandler.java | 10 + .../flowable/delegate/handler/HpcHandler.java | 36 ++ .../delegate/handler/HttpHandler.java | 17 + .../delegate/handler/LocalAppHandler.java | 16 + .../flowable/dto/ExtensionElementsDTO.java | 15 + .../com/sdm/flowable/dto/FlowElementDTO.java | 34 ++ .../java/com/sdm/flowable/dto/ProcessDTO.java | 14 + .../flowable/dto/ProcessDefinitionDTO.java | 15 + .../dto/req/AsyncCallbackRequest.java | 24 + .../sdm/flowable/dto/req/CompleteTaskReq.java | 13 + .../dto/req/DeployFlowableJsonReq.java | 8 + .../dto/resp/ProcessInstanceResp.java | 18 + .../sdm/flowable/entity/AsyncTaskRecord.java | 66 +++ .../sdm/flowable/entity/ProcessNodeParam.java | 54 ++ .../flowable/enums/FlowElementTypeEnums.java | 28 + .../sdm/flowable/process/ProcessService.java | 509 ++++++++++++++++++ .../service/IAsyncTaskRecordService.java | 28 + .../service/IProcessNodeParamService.java | 19 + .../impl/AsyncTaskRecordServiceImpl.java | 71 +++ .../impl/ProcessNodeParamServiceImpl.java | 78 +++ .../sdm/flowable/util/Dto2BpmnConverter.java | 378 +++++++++++++ flowable/src/main/resources/application.yml | 36 ++ flowable/src/main/resources/flow.json | 160 ++++++ .../mapper/AsyncTaskRecordMapper.xml | 5 + .../mapper/ProcessNodeParamMapper.xml | 5 + .../flowable/FlowableApplicationTests.java | 13 + 51 files changed, 2842 insertions(+), 3 deletions(-) create mode 100644 flowable/.gitattributes create mode 100644 flowable/.gitignore create mode 100644 flowable/.mvn/wrapper/maven-wrapper.properties create mode 100644 flowable/mvnw create mode 100644 flowable/mvnw.cmd create mode 100644 flowable/pom.xml create mode 100644 flowable/sql/async_task_record.sql create mode 100644 flowable/sql/process_node_param.sql create mode 100644 flowable/src/main/java/com/sdm/flowable/FlowableApplication.java create mode 100644 flowable/src/main/java/com/sdm/flowable/config/executeConfig/BaseExecuteConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/config/executeConfig/CloudAppExecuteConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/config/executeConfig/DataProcessExecuteConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/config/executeConfig/DefaultExecuteConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/config/executeConfig/HPCExecuteConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/config/executeConfig/HttpExecuteConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/config/executeConfig/LocalAppExecuteConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/constants/FlowableConfig.java create mode 100644 flowable/src/main/java/com/sdm/flowable/controller/ProcessController.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dao/AsyncTaskRecordMapper.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dao/ProcessNodeParamMapper.java create mode 100644 flowable/src/main/java/com/sdm/flowable/delegate/UniversalDelegate.java create mode 100644 flowable/src/main/java/com/sdm/flowable/delegate/handler/CloudAppHandler.java create mode 100644 flowable/src/main/java/com/sdm/flowable/delegate/handler/DataProcessHandler.java create mode 100644 flowable/src/main/java/com/sdm/flowable/delegate/handler/ExecutionHandler.java create mode 100644 flowable/src/main/java/com/sdm/flowable/delegate/handler/HpcHandler.java create mode 100644 flowable/src/main/java/com/sdm/flowable/delegate/handler/HttpHandler.java create mode 100644 flowable/src/main/java/com/sdm/flowable/delegate/handler/LocalAppHandler.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/ExtensionElementsDTO.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/FlowElementDTO.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/ProcessDTO.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/ProcessDefinitionDTO.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/req/AsyncCallbackRequest.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/req/CompleteTaskReq.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/req/DeployFlowableJsonReq.java create mode 100644 flowable/src/main/java/com/sdm/flowable/dto/resp/ProcessInstanceResp.java create mode 100644 flowable/src/main/java/com/sdm/flowable/entity/AsyncTaskRecord.java create mode 100644 flowable/src/main/java/com/sdm/flowable/entity/ProcessNodeParam.java create mode 100644 flowable/src/main/java/com/sdm/flowable/enums/FlowElementTypeEnums.java create mode 100644 flowable/src/main/java/com/sdm/flowable/process/ProcessService.java create mode 100644 flowable/src/main/java/com/sdm/flowable/service/IAsyncTaskRecordService.java create mode 100644 flowable/src/main/java/com/sdm/flowable/service/IProcessNodeParamService.java create mode 100644 flowable/src/main/java/com/sdm/flowable/service/impl/AsyncTaskRecordServiceImpl.java create mode 100644 flowable/src/main/java/com/sdm/flowable/service/impl/ProcessNodeParamServiceImpl.java create mode 100644 flowable/src/main/java/com/sdm/flowable/util/Dto2BpmnConverter.java create mode 100644 flowable/src/main/resources/application.yml create mode 100644 flowable/src/main/resources/flow.json create mode 100644 flowable/src/main/resources/mapper/AsyncTaskRecordMapper.xml create mode 100644 flowable/src/main/resources/mapper/ProcessNodeParamMapper.xml create mode 100644 flowable/src/test/java/com/sdm/flowable/FlowableApplicationTests.java diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 140cf895..86039680 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -15,7 +15,6 @@ - @@ -23,6 +22,7 @@ + diff --git a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java index a3ef8039..12ccf590 100644 --- a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java +++ b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java @@ -1572,8 +1572,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { } String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1); - if (!"jpg,jpeg,gif,png,bmp,webp".toUpperCase().contains(suffix.toUpperCase())) { - return SdmResponse.failed("请选择jpg,jpeg,gif,png,bmp,webp格式的图片"); + if (!"jpg,jpeg,gif,png,bmp,webp,svg".toUpperCase().contains(suffix.toUpperCase())) { + return SdmResponse.failed("请选择jpg,jpeg,gif,png,bmp,webp,svg格式的图片"); } // 获取 avatar 目录的 id diff --git a/flowable/.gitattributes b/flowable/.gitattributes new file mode 100644 index 00000000..3b41682a --- /dev/null +++ b/flowable/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/flowable/.gitignore b/flowable/.gitignore new file mode 100644 index 00000000..667aaef0 --- /dev/null +++ b/flowable/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/flowable/.mvn/wrapper/maven-wrapper.properties b/flowable/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..c0bcafe9 --- /dev/null +++ b/flowable/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/flowable/mvnw b/flowable/mvnw new file mode 100644 index 00000000..bd8896bf --- /dev/null +++ b/flowable/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/flowable/mvnw.cmd b/flowable/mvnw.cmd new file mode 100644 index 00000000..92450f93 --- /dev/null +++ b/flowable/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/flowable/pom.xml b/flowable/pom.xml new file mode 100644 index 00000000..fc92d94c --- /dev/null +++ b/flowable/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + com.sdm + SDM + 1.0-SNAPSHOT + + com.sdm + flowable + 0.0.1-SNAPSHOT + flowable + flowable + + + 17 + UTF-8 + UTF-8 + 3.3.5 + 7.0.1 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + + com.sdm + common + 0.0.1-SNAPSHOT + + + * + * + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.flowable + flowable-spring-boot-starter + ${flowable.version} + + + + com.google.guava + guava + 31.1-jre + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/flowable/sql/async_task_record.sql b/flowable/sql/async_task_record.sql new file mode 100644 index 00000000..a7fbd2a1 --- /dev/null +++ b/flowable/sql/async_task_record.sql @@ -0,0 +1,19 @@ +-- 异步任务执行记录表 +-- 用于存储异步任务(如HPC/OCR/AI等)的全生命周期信息,包括任务标识、流程关联信息、执行状态、请求/结果数据等 +CREATE TABLE async_task_record +( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID,自增', + async_task_id VARCHAR(64) NOT NULL UNIQUE COMMENT '异步任务唯一标识,全局唯一,用于定位单个异步任务', + process_instance_id VARCHAR(64) COMMENT '流程实例ID,关联工作流引擎的流程实例(如Camunda的processInstanceId)', + execution_id VARCHAR(64) COMMENT '流程执行ID,关联工作流引擎的执行实例(如Camunda的executionId)', + receive_task_id VARCHAR(64) COMMENT '接收任务ID,关联工作流中接收任务节点的ID(用于异步回调触发流程继续)', + handler_type VARCHAR(64) COMMENT '业务处理器类型,标识任务对应的业务处理逻辑,例如:HPC(高性能计算)/OCR(图文识别)/AI(智能分析)', + request_json TEXT COMMENT '任务请求参数,JSON格式字符串,存储触发异步任务时的入参信息', + result_json TEXT COMMENT '任务执行结果,JSON格式字符串,存储异步任务完成后的返回数据(成功/失败均记录)', + status VARCHAR(32) DEFAULT 'INIT' COMMENT '任务状态:INIT(初始化)/RUNNING(执行中)/SUCCESS(执行成功)/FAIL(执行失败)', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '任务创建时间,默认当前时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '任务更新时间,数据变更时自动更新为当前时间' +) COMMENT = '异步任务执行记录表'; + +-- 异步任务ID索引:优化根据async_task_id查询任务信息的性能(高频查询场景) +CREATE INDEX idx_async_task_id ON async_task_record (async_task_id) COMMENT '异步任务ID索引,加速任务唯一标识的查询'; \ No newline at end of file diff --git a/flowable/sql/process_node_param.sql b/flowable/sql/process_node_param.sql new file mode 100644 index 00000000..b2b5b3af --- /dev/null +++ b/flowable/sql/process_node_param.sql @@ -0,0 +1,12 @@ +CREATE TABLE `flowable`.`process_node_param` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `processDefinitionId` varchar(64) DEFAULT NULL COMMENT '流程定义ID', + `nodeId` varchar(64) DEFAULT NULL COMMENT '节点ID', + `paramJson` text COMMENT '输入参数JSON', + `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_processDefinitionId_nodeId` (`processDefinitionId`,`nodeId`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='流程节点输入参数表'; + diff --git a/flowable/src/main/java/com/sdm/flowable/FlowableApplication.java b/flowable/src/main/java/com/sdm/flowable/FlowableApplication.java new file mode 100644 index 00000000..1614266c --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/FlowableApplication.java @@ -0,0 +1,21 @@ +package com.sdm.flowable; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication(scanBasePackages = {"com.sdm.flowable","com.sdm.common"}) +@EnableDiscoveryClient +@EnableScheduling +@EnableFeignClients(basePackages = "com.sdm.common.feign") +@MapperScan("com.sdm.flowable.dao") +public class FlowableApplication { + + public static void main(String[] args) { + SpringApplication.run(FlowableApplication.class, args); + } + +} diff --git a/flowable/src/main/java/com/sdm/flowable/config/executeConfig/BaseExecuteConfig.java b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/BaseExecuteConfig.java new file mode 100644 index 00000000..b0e731d5 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/BaseExecuteConfig.java @@ -0,0 +1,35 @@ +package com.sdm.flowable.config.executeConfig; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; + +/** + * 执行配置父类(带Jackson子类自动识别注解) + */ +@Data +// 核心注解:指定类型识别规则 +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, // 用「名称」识别子类 + include = JsonTypeInfo.As.PROPERTY, // 类型信息作为JSON的一个属性(即executeType字段) + property = "executeType", // 类型字段名(必须和JSON中的executeType一致) + visible = true, // 必须为true,否则JSON中将不包含executeType字段 + defaultImpl = DefaultExecuteConfig.class // 默认实现(当executeType不匹配时,用这个类接收,避免报错) +) +// 核心注解:指定「executeType值」和「子类」的映射关系 +@JsonSubTypes({ + @JsonSubTypes.Type(value = CloudAppExecuteConfig.class, name = "cloudApp"), + @JsonSubTypes.Type(value = LocalAppExecuteConfig.class, name = "localApp"), + @JsonSubTypes.Type(value = HPCExecuteConfig.class, name = "HPC"), + @JsonSubTypes.Type(value = HttpExecuteConfig.class, name = "http"), + @JsonSubTypes.Type(value = DataProcessExecuteConfig.class, name = "dataProcess") +}) +public abstract class BaseExecuteConfig { + // 公共字段:executeType(子类无需重复定义,父类统一维护) + private String executeType; + + // 用于标记是否异步回调,处理长耗时任务,默认是false 表示当前执行节点是同步执行 + private boolean asyncCallback = false; + // 用于标记回调节点ID,当asyncCallback为true时,表示当前执行节点是异步执行,执行完成后需要回调的节点ID,一般就是receiveTaskId + private String callbackNodeId; +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/config/executeConfig/CloudAppExecuteConfig.java b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/CloudAppExecuteConfig.java new file mode 100644 index 00000000..06e44ca9 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/CloudAppExecuteConfig.java @@ -0,0 +1,4 @@ +package com.sdm.flowable.config.executeConfig; + +public class CloudAppExecuteConfig extends BaseExecuteConfig { +} diff --git a/flowable/src/main/java/com/sdm/flowable/config/executeConfig/DataProcessExecuteConfig.java b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/DataProcessExecuteConfig.java new file mode 100644 index 00000000..ae0cd0a9 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/DataProcessExecuteConfig.java @@ -0,0 +1,11 @@ +package com.sdm.flowable.config.executeConfig; + +import lombok.Data; + +/** + * DATA_PROCESS类型执行配置(executeType=dataProcess) + */ +@Data +public class DataProcessExecuteConfig extends BaseExecuteConfig { + private String processRule; // 数据处理规则(如mergeAndFilter) +} diff --git a/flowable/src/main/java/com/sdm/flowable/config/executeConfig/DefaultExecuteConfig.java b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/DefaultExecuteConfig.java new file mode 100644 index 00000000..a9754a26 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/DefaultExecuteConfig.java @@ -0,0 +1,11 @@ +package com.sdm.flowable.config.executeConfig; + +import lombok.Data; + +/** + * 默认执行配置(executeType不匹配时使用) + */ +@Data +public class DefaultExecuteConfig extends BaseExecuteConfig { + // 空实现,仅用于兼容未知执行类型 +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/config/executeConfig/HPCExecuteConfig.java b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/HPCExecuteConfig.java new file mode 100644 index 00000000..dadd4b81 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/HPCExecuteConfig.java @@ -0,0 +1,4 @@ +package com.sdm.flowable.config.executeConfig; + +public class HPCExecuteConfig extends BaseExecuteConfig { +} diff --git a/flowable/src/main/java/com/sdm/flowable/config/executeConfig/HttpExecuteConfig.java b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/HttpExecuteConfig.java new file mode 100644 index 00000000..3f6fbef9 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/HttpExecuteConfig.java @@ -0,0 +1,15 @@ +package com.sdm.flowable.config.executeConfig; + +import lombok.Data; + +import java.util.Map; + +/** + * HTTP类型执行配置(executeType=http) + */ +@Data +public class HttpExecuteConfig extends BaseExecuteConfig { + private String httpMethod; // 请求方法(GET/POST) + private String httpUrl; // 请求地址 + private Map headers; // 请求头(键值对) +} diff --git a/flowable/src/main/java/com/sdm/flowable/config/executeConfig/LocalAppExecuteConfig.java b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/LocalAppExecuteConfig.java new file mode 100644 index 00000000..5990505b --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/config/executeConfig/LocalAppExecuteConfig.java @@ -0,0 +1,4 @@ +package com.sdm.flowable.config.executeConfig; + +public class LocalAppExecuteConfig extends BaseExecuteConfig { +} diff --git a/flowable/src/main/java/com/sdm/flowable/constants/FlowableConfig.java b/flowable/src/main/java/com/sdm/flowable/constants/FlowableConfig.java new file mode 100644 index 00000000..5a95c946 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/constants/FlowableConfig.java @@ -0,0 +1,8 @@ +package com.sdm.flowable.constants; + +public interface FlowableConfig { + /* + * 前端流程节点自定义执行参数key + */ + String EXECUTECONFIG = "executeConfig"; +} diff --git a/flowable/src/main/java/com/sdm/flowable/controller/ProcessController.java b/flowable/src/main/java/com/sdm/flowable/controller/ProcessController.java new file mode 100644 index 00000000..13fbda64 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/controller/ProcessController.java @@ -0,0 +1,253 @@ +package com.sdm.flowable.controller; + +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sdm.common.common.SdmResponse; +import com.sdm.flowable.dto.ProcessDefinitionDTO; +import com.sdm.flowable.dto.resp.ProcessInstanceResp; +import com.sdm.flowable.process.ProcessService; +import com.sdm.flowable.service.IProcessNodeParamService; +import com.sdm.flowable.dto.req.CompleteTaskReq; +import com.sdm.flowable.dto.req.AsyncCallbackRequest; +import com.sdm.flowable.delegate.UniversalDelegate; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.validation.ValidationError; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/process") +public class ProcessController { + @Autowired + private ProcessService processService; + + @Autowired + private IProcessNodeParamService processNodeParamService; + + @Autowired + private UniversalDelegate universalDelegate; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + // 验证流程模型 + @PostMapping("/validate") + public Map validate(@RequestBody ProcessDefinitionDTO processDTO) { + Map result = new HashMap<>(); + + try { + List errors = processService.validateModel(processDTO); + if (errors.isEmpty()) { + result.put("valid", true); + result.put("message", "模型验证通过"); + } else { + result.put("valid", false); + result.put("errors", errors); + } + } catch (Exception e) { + result.put("valid", false); + result.put("message", "验证过程中发生错误: " + e.getMessage()); + } + + return result; + } + + + // 部署流程 + @PostMapping("/deploy") + public Map deploy(@RequestBody ProcessDefinitionDTO processDTO) { + Map result = new HashMap<>(); + + try { + Deployment deployment = processService.deploy(processDTO); + result.put("success", true); + result.put("deploymentId", deployment.getId()); + result.put("deploymentName", deployment.getName()); + } catch (Exception e) { + result.put("success", false); + result.put("message", "部署失败: " + e.getMessage()); + } + + return result; + } + + /** + * 获取所有流程的部署历史 + * + * @return 流程部署数据 + */ + @GetMapping("/listAllDeployments") + public List> listAllDeployments() { + return processService.listAllDeployments(); + } + + /** + * 根据部署ID获取流程定义 + * + * @param deploymentId 部署ID + * @return 流程定义列表 + */ + @GetMapping("/listPorcessDefinitionsByDeploymentId/{deploymentId}") + public List> listPorcessDefinitionsByDeploymentId(@PathVariable String deploymentId) { + return processService.listPorcessDefinitionsByDeploymentId(deploymentId); + } + + + /** + * 获取部署的流程定义元数据信息 + * 1、查询所有流程定义(所有版本) + * 2、查询所有流程的“最新版本” + * 3、查询某个流程的所有版本 + * 4、查询某个流程的最新版本 + * 5、查询版本从大到小 + * + * @param processDefinitionKey + * @param latest + * @param order + * @param page + * @param size + * @return + */ + @GetMapping("/listProcessDefinitionsMetaInfo") + public Map 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) { + + return processService.listProcessDefinitionsMetaInfo(processDefinitionKey, latest, order, page, size); + } + + /** + * 根据流程 key 和指定版本获取版本流程定义的节点信息 + */ + @GetMapping("/listNodesByProcessDefinitionKey") + public List> listNodesByProcessDefinitionKey(@RequestParam String processDefinitionKey,@RequestParam(required = false)Integer processDefinitionVersion) { + return processService.listNodesByProcessDefinitionKey(processDefinitionKey,processDefinitionVersion); + } + + /** + * 获取流程定义节点详细信息(直接传流程定义ID) + */ + @GetMapping("/listNodesByProcessDefinitionId") + public List> listNodesByProcessDefinitionId( + @RequestParam String processDefinitionId) { + + return processService.getNodesByProcessDefinitionId(processDefinitionId); + } + + + + + /** + * 删除所有流程部署 + */ + @GetMapping("/deleteAllDeployments") + public void deleteAllDeployments() { + processService.deleteAllDeployments(); + } + + + // 保存节点用户输入参数(基于流程定义ID,作为参数模板) + @PostMapping("/saveParamsByDefinitionId") + public void saveParamsByDefinitionId(@RequestParam String processDefinitionId, @RequestParam String nodeId, + @RequestBody Map params) { + processNodeParamService.saveParamByDefinitionId(processDefinitionId, nodeId, params); + } + + // 启动流程实例 + @GetMapping("/startByProcessDefinitionKey") + public SdmResponse startByProcessDefinitionKey(@RequestParam String processDefinitionKey) { + ProcessInstance processInstance = processService.startByProcessDefinitionKey(processDefinitionKey); + ProcessInstanceResp processInstanceResp = new ProcessInstanceResp(); + processInstanceResp.setId(processInstance.getId()); + processInstanceResp.setProcessDefinitionId(processInstance.getProcessDefinitionId()); + processInstanceResp.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); + processInstanceResp.setProcessDefinitionName(processInstance.getProcessDefinitionName()); + processInstanceResp.setProcessDefinitionVersion(processInstance.getProcessDefinitionVersion()); + processInstanceResp.setBusinessKey(processInstance.getBusinessKey()); + processInstanceResp.setStartUserId(processInstance.getStartUserId()); + processInstanceResp.setStartTime(processInstance.getStartTime()); + processInstanceResp.setSuspended(processInstance.isSuspended()); + return SdmResponse.success(processInstanceResp); + } + + /** + * 根据流程定义ID启动流程实例 + * + * @param processDefinitionId 流程定义ID(指定版本) + * @param variables 可选的流程启动变量 + */ + @PostMapping("/startByProcessDefinitionId") + public SdmResponse startByProcessDefinitionId( + @RequestParam String processDefinitionId, + @RequestBody(required = false) Map variables) { + ProcessInstance processInstance = processService.startByProcessDefinitionId(processDefinitionId, variables); + ProcessInstanceResp processInstanceResp = new ProcessInstanceResp(); + processInstanceResp.setId(processInstance.getId()); + processInstanceResp.setProcessDefinitionId(processInstance.getProcessDefinitionId()); + processInstanceResp.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); + processInstanceResp.setProcessDefinitionName(processInstance.getProcessDefinitionName()); + processInstanceResp.setProcessDefinitionVersion(processInstance.getProcessDefinitionVersion()); + processInstanceResp.setBusinessKey(processInstance.getBusinessKey()); + processInstanceResp.setStartUserId(processInstance.getStartUserId()); + processInstanceResp.setStartTime(processInstance.getStartTime()); + processInstanceResp.setSuspended(processInstance.isSuspended()); + return SdmResponse.success(processInstanceResp); + } + + /** + * 获取最新流程实例 + * + * @param processDefinitionKey + * @return + */ + @GetMapping("/getLatestProcessInstanceByProcessDefinitionKey") + public HistoricProcessInstance getLatestProcessInstanceByProcessDefinitionKey(@RequestParam String processDefinitionKey) { + return processService.getLatestProcessInstance(processDefinitionKey); + } + + /** + * 根据流程实例 ID 查询节点状态 + */ + @GetMapping("/getNodeDetailByInstanceId") + public Map getNodeDetailByInstanceId(@RequestParam String processInstanceId) { + return processService.getProcessNodeDetail(processInstanceId); + } + + /** + * 根据流程定义 Key 查询最新流程实例节点状态 + */ + @GetMapping("/getNodeDetailByProcessDefinitionKey") + public Map getNodeDetailByProcessDefinitionKey(@RequestParam String processDefinitionKey) { + return processService.getNodeDetailByProcessDefinitionKey(processDefinitionKey); + } + + /** + * 完成人工节点任务 + * + * @param req + * @return + */ + @PostMapping("/completeManualTasks") + public void completeManualTasks(@RequestBody CompleteTaskReq req) { + processService.completeManualTasks(req); + } + + /** + * 异步任务回调接口,用于唤醒等待的流程实例 + * + * @param request 包含异步任务ID和执行结果的请求对象 + */ + @PostMapping("/asyncCallback") + public void asyncCallback(@RequestBody AsyncCallbackRequest request) { + // 发送信号唤醒流程实例中等待的节点 + universalDelegate.signalByTaskId(request); + } +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/dao/AsyncTaskRecordMapper.java b/flowable/src/main/java/com/sdm/flowable/dao/AsyncTaskRecordMapper.java new file mode 100644 index 00000000..b62774e6 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dao/AsyncTaskRecordMapper.java @@ -0,0 +1,16 @@ +package com.sdm.flowable.dao; + +import com.sdm.flowable.entity.AsyncTaskRecord; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 异步任务执行记录表 Mapper 接口 + *

+ * + * @author author + * @since 2025-11-26 + */ +public interface AsyncTaskRecordMapper extends BaseMapper { + +} diff --git a/flowable/src/main/java/com/sdm/flowable/dao/ProcessNodeParamMapper.java b/flowable/src/main/java/com/sdm/flowable/dao/ProcessNodeParamMapper.java new file mode 100644 index 00000000..fd4c1285 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dao/ProcessNodeParamMapper.java @@ -0,0 +1,16 @@ +package com.sdm.flowable.dao; + +import com.sdm.flowable.entity.ProcessNodeParam; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 流程节点输入参数表 Mapper 接口 + *

+ * + * @author author + * @since 2025-11-25 + */ +public interface ProcessNodeParamMapper extends BaseMapper { + +} diff --git a/flowable/src/main/java/com/sdm/flowable/delegate/UniversalDelegate.java b/flowable/src/main/java/com/sdm/flowable/delegate/UniversalDelegate.java new file mode 100644 index 00000000..aa64405f --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/delegate/UniversalDelegate.java @@ -0,0 +1,94 @@ +package com.sdm.flowable.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import com.sdm.flowable.constants.FlowableConfig; +import com.sdm.flowable.delegate.handler.ExecutionHandler; +import com.sdm.flowable.dto.req.AsyncCallbackRequest; +import com.sdm.flowable.service.IAsyncTaskRecordService; +import com.sdm.flowable.service.IProcessNodeParamService; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.flowable.engine.RuntimeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 通用执行器,用于处理不同类型的节点(如 userTask, serviceTask)的执行逻辑。 + */ +@Component("universalDelegate") +@Slf4j +public class UniversalDelegate implements JavaDelegate { + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private IProcessNodeParamService paramService; + + @Autowired + private IAsyncTaskRecordService asyncTaskRecordService; + + @Autowired + private Map handlerMap; // 执行器映射 + + @Override + public void execute(DelegateExecution execution) { + try { + // 1. 获取当前节点信息 + String procInstId = execution.getProcessInstanceId(); + String nodeId = execution.getCurrentActivityId(); + String nodeName = execution.getCurrentFlowElement().getName(); + + // 2. 读取输入参数 + Map params = paramService.getParam(procInstId, nodeId); + + log.info("==== 节点执行日志 ====\n流程实例ID:{}\n节点ID:{}\n节点名称:{}\n输入参数:{}\n====================", + procInstId, nodeId, nodeName, params); + + // 检查是否有扩展元素配置 + if (execution.getCurrentFlowElement().getExtensionElements() != null && + execution.getCurrentFlowElement().getExtensionElements().get(FlowableConfig.EXECUTECONFIG) != null) { + + String extensionElement = execution + .getCurrentFlowElement() + .getExtensionElements() + .get(FlowableConfig.EXECUTECONFIG).get(0).getElementText(); + + BaseExecuteConfig config = + objectMapper.readValue(extensionElement, BaseExecuteConfig.class); + + String executeType = config.getExecuteType(); + ExecutionHandler handler = handlerMap.get(executeType); + if (handler == null) { + throw new RuntimeException("不支持的执行方式:" + executeType); + } + + // 执行具体的任务处理逻辑 + handler.execute(execution, params, config); + } else { + // 对于没有配置 executeConfig 的节点(如 userTask),直接完成任务 + log.info("节点 {} 没有执行配置,直接完成任务", nodeName); + } + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * 外部系统回调接口,用于唤醒等待的流程实例 + * @param processInstanceId 流程实例ID + * @param nodeId 节点ID + * @param resultData 结果数据 + */ + /** + * HPC 回调接口,用于唤醒等待的流程 + */ + public void signalByTaskId(AsyncCallbackRequest request) { + asyncTaskRecordService.completeAsyncTask(request); + } + +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/delegate/handler/CloudAppHandler.java b/flowable/src/main/java/com/sdm/flowable/delegate/handler/CloudAppHandler.java new file mode 100644 index 00000000..6c65a54f --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/delegate/handler/CloudAppHandler.java @@ -0,0 +1,16 @@ +package com.sdm.flowable.delegate.handler; + +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.Map; + +// 云应用处理器(executeType=cloudApp) +@Component("cloudApp") +public class CloudAppHandler implements ExecutionHandler { + @Override + public void execute(DelegateExecution execution, Map params, BaseExecuteConfig config) { + // 实现云应用处理逻辑... + } +} diff --git a/flowable/src/main/java/com/sdm/flowable/delegate/handler/DataProcessHandler.java b/flowable/src/main/java/com/sdm/flowable/delegate/handler/DataProcessHandler.java new file mode 100644 index 00000000..e2f76ced --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/delegate/handler/DataProcessHandler.java @@ -0,0 +1,16 @@ +package com.sdm.flowable.delegate.handler; + +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.Map; + +// 数据处理执行器(executeType=data_process) +@Component("dataProcess") +public class DataProcessHandler implements ExecutionHandler { + @Override + public void execute(DelegateExecution execution, Map params, BaseExecuteConfig config) { + // 实现数据处理逻辑... + } +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/delegate/handler/ExecutionHandler.java b/flowable/src/main/java/com/sdm/flowable/delegate/handler/ExecutionHandler.java new file mode 100644 index 00000000..726246b1 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/delegate/handler/ExecutionHandler.java @@ -0,0 +1,10 @@ +package com.sdm.flowable.delegate.handler; + +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import org.flowable.engine.delegate.DelegateExecution; + +import java.util.Map; + +public interface ExecutionHandler { + void execute(DelegateExecution execution, Map params, BaseExecuteConfig config); +} diff --git a/flowable/src/main/java/com/sdm/flowable/delegate/handler/HpcHandler.java b/flowable/src/main/java/com/sdm/flowable/delegate/handler/HpcHandler.java new file mode 100644 index 00000000..99a643ed --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/delegate/handler/HpcHandler.java @@ -0,0 +1,36 @@ +package com.sdm.flowable.delegate.handler; + +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import com.sdm.flowable.service.IAsyncTaskRecordService; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +// HPC(executeType=HPC) +@Slf4j +@Component("HPC") +public class HpcHandler implements ExecutionHandler { + @Autowired + private IAsyncTaskRecordService asyncTaskRecordService; + + @Override + public void execute(DelegateExecution execution, Map params, BaseExecuteConfig config) { + // 实现HPC处理逻辑... + // 1. 调用 HPC 平台提交任务 + // String hpcTaskId = submitHpcTask(params); + String hpcTaskId = ""; + // 2. 存数据库(提交状态 + 外部任务ID) + asyncTaskRecordService.registerAsyncTask( + execution, + config.getCallbackNodeId(), // ReceiveTask ID + "HPC", // handlerType + new HashMap<>() + ); + + log.info("HPC 任务 {} 已提交", hpcTaskId); + } +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/delegate/handler/HttpHandler.java b/flowable/src/main/java/com/sdm/flowable/delegate/handler/HttpHandler.java new file mode 100644 index 00000000..93f21121 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/delegate/handler/HttpHandler.java @@ -0,0 +1,17 @@ +package com.sdm.flowable.delegate.handler; + +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.Map; + +// HTTP请求执行器(executeType=HTTP) +@Component("http") +public class HttpHandler implements ExecutionHandler { + + @Override + public void execute(DelegateExecution execution, Map params, BaseExecuteConfig config) { + // 实现HTTP请求逻辑... + } +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/delegate/handler/LocalAppHandler.java b/flowable/src/main/java/com/sdm/flowable/delegate/handler/LocalAppHandler.java new file mode 100644 index 00000000..ae74d7ff --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/delegate/handler/LocalAppHandler.java @@ -0,0 +1,16 @@ +package com.sdm.flowable.delegate.handler; + +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.Map; + +// 本地应用处理器(executeType=localApp) +@Component("localApp") +public class LocalAppHandler implements ExecutionHandler { + @Override + public void execute(DelegateExecution execution, Map params, BaseExecuteConfig config) { + // 实现本地应用处理逻辑... + } +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/dto/ExtensionElementsDTO.java b/flowable/src/main/java/com/sdm/flowable/dto/ExtensionElementsDTO.java new file mode 100644 index 00000000..95e416f9 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/ExtensionElementsDTO.java @@ -0,0 +1,15 @@ +package com.sdm.flowable.dto; + +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import lombok.Data; + +/** + * 扩展配置DTO(serviceTask等需要执行配置的节点使用) + */ +@Data +public class ExtensionElementsDTO { + /** + * 执行配置(核心字段,对应executeConfig) + */ + private BaseExecuteConfig executeConfig; +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/dto/FlowElementDTO.java b/flowable/src/main/java/com/sdm/flowable/dto/FlowElementDTO.java new file mode 100644 index 00000000..ec4722dd --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/FlowElementDTO.java @@ -0,0 +1,34 @@ +package com.sdm.flowable.dto; + +import lombok.Data; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; + +/** + * 流元素DTO(统一接收节点和连线,通过type区分) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) // 忽略不同类型的多余字段(关键) +public class FlowElementDTO { + // ------------------------------ 所有流元素通用字段 ------------------------------ + /** 元素唯一ID(流程内不重复) */ + private String id; + /** 元素类型:startEvent/serviceTask/userTask/endEvent/sequenceFlow */ + private String type; + /** 元素显示名称 */ + private String name; + + // ------------------------------ 节点专属字段(startEvent/serviceTask/userTask/endEvent) ------------------------------ + /** 入连线ID集合(仅节点有) */ + private List incomingFlows; + /** 出连线ID集合(仅节点有) */ + private List outgoingFlows; + /** 扩展配置(serviceTask等需要执行配置的节点使用) */ + private ExtensionElementsDTO extensionElements; + + // ------------------------------ 连线专属字段(sequenceFlow) ------------------------------ + /** 源节点ID(仅连线有) */ + private String sourceRef; + /** 目标节点ID(仅连线有) */ + private String targetRef; +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/dto/ProcessDTO.java b/flowable/src/main/java/com/sdm/flowable/dto/ProcessDTO.java new file mode 100644 index 00000000..93760dc0 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/ProcessDTO.java @@ -0,0 +1,14 @@ +package com.sdm.flowable.dto; + +import lombok.Data; + +/** + * 流程基础信息DTO(对应JSON中的process字段) + */ +@Data +public class ProcessDTO { + /** 流程唯一ID(必填) */ + private String id; + /** 流程名称(必填) */ + private String name; +} diff --git a/flowable/src/main/java/com/sdm/flowable/dto/ProcessDefinitionDTO.java b/flowable/src/main/java/com/sdm/flowable/dto/ProcessDefinitionDTO.java new file mode 100644 index 00000000..5fb36c2b --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/ProcessDefinitionDTO.java @@ -0,0 +1,15 @@ +package com.sdm.flowable.dto; + +import lombok.Data; +import java.util.List; + +/** + * 流程定义顶层DTO(对应整个JSON) + */ +@Data +public class ProcessDefinitionDTO { + /** 流程核心信息 */ + private ProcessDTO process; + /** 所有节点+连线集合 */ + private List flowElements; +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/dto/req/AsyncCallbackRequest.java b/flowable/src/main/java/com/sdm/flowable/dto/req/AsyncCallbackRequest.java new file mode 100644 index 00000000..e36d553a --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/req/AsyncCallbackRequest.java @@ -0,0 +1,24 @@ +package com.sdm.flowable.dto.req; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.io.Serializable; + +/** + * 异步任务回调请求参数 + */ +@Data +public class AsyncCallbackRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 异步任务ID + */ + private String asyncTaskId; + + /** + * 任务执行结果数据 + */ + private String resultJson; +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/dto/req/CompleteTaskReq.java b/flowable/src/main/java/com/sdm/flowable/dto/req/CompleteTaskReq.java new file mode 100644 index 00000000..be29de1b --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/req/CompleteTaskReq.java @@ -0,0 +1,13 @@ +package com.sdm.flowable.dto.req; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class CompleteTaskReq { + private String processInstanceId; + private String taskDefinitionKey; + private Map variables = new HashMap<>(); +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/dto/req/DeployFlowableJsonReq.java b/flowable/src/main/java/com/sdm/flowable/dto/req/DeployFlowableJsonReq.java new file mode 100644 index 00000000..5cefdf52 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/req/DeployFlowableJsonReq.java @@ -0,0 +1,8 @@ +package com.sdm.flowable.dto.req; + +import lombok.Data; + +@Data +public class DeployFlowableJsonReq { + String flowableJson; +} diff --git a/flowable/src/main/java/com/sdm/flowable/dto/resp/ProcessInstanceResp.java b/flowable/src/main/java/com/sdm/flowable/dto/resp/ProcessInstanceResp.java new file mode 100644 index 00000000..13d291a7 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/dto/resp/ProcessInstanceResp.java @@ -0,0 +1,18 @@ +package com.sdm.flowable.dto.resp; + +import lombok.Data; + +import java.util.Date; + +@Data +public class ProcessInstanceResp { + private String id; // 流程实例ID + private String processDefinitionId; // 流程定义ID + private String processDefinitionKey; // 流程定义Key + private String processDefinitionName;// 流程定义名称 + private int processDefinitionVersion; // 流程定义版本 + private String businessKey; // 业务Key + private String startUserId; // 启动用户 + private Date startTime; // 启动时间 + private boolean suspended; // 是否挂起 +} diff --git a/flowable/src/main/java/com/sdm/flowable/entity/AsyncTaskRecord.java b/flowable/src/main/java/com/sdm/flowable/entity/AsyncTaskRecord.java new file mode 100644 index 00000000..21bcaf8d --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/entity/AsyncTaskRecord.java @@ -0,0 +1,66 @@ +package com.sdm.flowable.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + *

+ * 异步任务执行记录表 + *

+ * + * @author author + * @since 2025-11-26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("async_task_record") +@ApiModel(value="AsyncTaskRecord对象", description="异步任务执行记录表") +public class AsyncTaskRecord implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "主键ID,自增") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "异步任务唯一标识,全局唯一,用于定位单个异步任务") + private String asyncTaskId; + + @ApiModelProperty(value = "流程实例ID,关联工作流引擎的流程实例(如Camunda的processInstanceId)") + private String processInstanceId; + + @ApiModelProperty(value = "流程执行ID,关联工作流引擎的执行实例(如Camunda的executionId)") + private String executionId; + + @ApiModelProperty(value = "接收任务ID,关联工作流中接收任务节点的ID(用于异步回调触发流程继续)") + private String receiveTaskId; + + @ApiModelProperty(value = "业务处理器类型,标识任务对应的业务处理逻辑,例如:HPC(高性能计算)/OCR(图文识别)/AI(智能分析)") + private String handlerType; + + @ApiModelProperty(value = "任务请求参数,JSON格式字符串,存储触发异步任务时的入参信息") + private String requestJson; + + @ApiModelProperty(value = "任务执行结果,JSON格式字符串,存储异步任务完成后的返回数据(成功/失败均记录)") + private String resultJson; + + @ApiModelProperty(value = "任务状态:INIT(初始化)/RUNNING(执行中)/SUCCESS(执行成功)/FAIL(执行失败)") + private String status; + + @ApiModelProperty(value = "任务创建时间,默认当前时间") + private LocalDateTime createTime; + + @ApiModelProperty(value = "任务更新时间,数据变更时自动更新为当前时间") + private LocalDateTime updateTime; + + +} diff --git a/flowable/src/main/java/com/sdm/flowable/entity/ProcessNodeParam.java b/flowable/src/main/java/com/sdm/flowable/entity/ProcessNodeParam.java new file mode 100644 index 00000000..011dbaa6 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/entity/ProcessNodeParam.java @@ -0,0 +1,54 @@ +package com.sdm.flowable.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + *

+ * 流程节点输入参数表 + *

+ * + * @author author + * @since 2025-11-25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("process_node_param") +@ApiModel(value="ProcessNodeParam对象", description="流程节点输入参数表") +public class ProcessNodeParam implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "流程定义ID") + @TableField("processDefinitionId") + private String processDefinitionId; + + @ApiModelProperty(value = "节点ID") + @TableField("nodeId") + private String nodeId; + + @ApiModelProperty(value = "输入参数JSON") + @TableField("paramJson") + private String paramJson; + + @TableField("createTime") + private LocalDateTime createTime; + + @TableField("updateTime") + private LocalDateTime updateTime; + + +} diff --git a/flowable/src/main/java/com/sdm/flowable/enums/FlowElementTypeEnums.java b/flowable/src/main/java/com/sdm/flowable/enums/FlowElementTypeEnums.java new file mode 100644 index 00000000..0f71d9a0 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/enums/FlowElementTypeEnums.java @@ -0,0 +1,28 @@ +package com.sdm.flowable.enums; + +public enum FlowElementTypeEnums { + STARTEVENT("startEvent"), + ENDEVENT("endEvent"), + USERTASK("userTask"), + SERVICETASK("serviceTask"), + EXCLUSIVEGATEWAY("exclusiveGateway"), + PARALLELGATEWAY("parallelGateway"), + SEQUENCEFLOW("sequenceFlow"); + private final String type; + + FlowElementTypeEnums(String startEvent) { + this.type = startEvent; + } + public String getType() { + return type; + } + + public static FlowElementTypeEnums fromString(String type) { + for (FlowElementTypeEnums flowElementType : FlowElementTypeEnums.values()) { + if (flowElementType.type.equals(type)) { + return flowElementType; + } + } + throw new IllegalArgumentException("Unknown type: " + type); + } +} diff --git a/flowable/src/main/java/com/sdm/flowable/process/ProcessService.java b/flowable/src/main/java/com/sdm/flowable/process/ProcessService.java new file mode 100644 index 00000000..fb0b9e4a --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/process/ProcessService.java @@ -0,0 +1,509 @@ +package com.sdm.flowable.process; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sdm.flowable.dto.ProcessDefinitionDTO; +import com.sdm.flowable.util.Dto2BpmnConverter; +import com.sdm.flowable.constants.FlowableConfig; +import com.sdm.flowable.dto.req.CompleteTaskReq; +import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.repository.ProcessDefinitionQuery; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.validation.ProcessValidator; +import org.flowable.validation.ProcessValidatorFactory; +import org.flowable.validation.ValidationError; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.*; +import java.util.stream.Collectors; + + +@Service +public class ProcessService { + @Autowired + private RepositoryService repositoryService; + + @Autowired + private RuntimeService runtimeService; + + @Autowired + private HistoryService historyService; + + @Autowired + private TaskService taskService; + + @Autowired + private Dto2BpmnConverter dto2BpmnConverter; + + // 部署流程(前端传入Flowable标准JSON) + public Deployment deploy(ProcessDefinitionDTO processDTO) throws Exception { + BpmnModel bpmnModel = dto2BpmnConverter.convert(processDTO); + // 检查BPMN模型是否有效 + if (bpmnModel.getProcesses().isEmpty()) { + throw new RuntimeException("无效的BPMN模型:未找到任何流程定义"); + } + + // 验证BPMN模型 + ProcessValidator validator = new ProcessValidatorFactory().createDefaultProcessValidator(); + List validationErrors = validator.validate(bpmnModel); + if (!validationErrors.isEmpty()) { + StringBuilder errorMsg = new StringBuilder("BPMN模型验证失败:"); + for (ValidationError error : validationErrors) { + errorMsg.append("\n - ").append(error.toString()); + } + throw new RuntimeException(errorMsg.toString()); + } + + return repositoryService.createDeployment() + .addBpmnModel("industrial_process.bpmn", bpmnModel) + .name("工业并行部署流程") + .deploy(); + } + + public List> listAllDeployments() { + return repositoryService.createDeploymentQuery() + .list() + .stream() + .map(deployment -> { + Map deploymentInfo = new HashMap<>(); + + deploymentInfo.put("id", deployment.getId()); + deploymentInfo.put("name", deployment.getName()); + deploymentInfo.put("deploymentTime", deployment.getDeploymentTime()); + deploymentInfo.put("category", deployment.getCategory()); + return deploymentInfo; + }) + .collect(Collectors.toList()); + } + + public List> listPorcessDefinitionsByDeploymentId(String deploymentId) { + + List processDefinitions = + repositoryService.createProcessDefinitionQuery() + .deploymentId(deploymentId) + .orderByProcessDefinitionVersion().asc() + .list(); + + return processDefinitions.stream() + .map(def -> { + Map map = new HashMap<>(); + map.put("id", def.getId()); + map.put("key", def.getKey()); + map.put("name", def.getName()); + map.put("resourceName", def.getResourceName()); + map.put("diagramResourceName", def.getDiagramResourceName()); + map.put("version", def.getVersion()); + map.put("suspended", def.isSuspended()); + map.put("deploymentId", def.getDeploymentId()); + return map; + }) + .collect(Collectors.toList()); + } + + public Map 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 list = query.listPage(page * size, size); + + // 转换成 DTO + List> data = list.stream() + .map(pd -> { + Map 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 result = new HashMap<>(); + result.put("total", total); + result.put("page", page); + result.put("size", size); + result.put("data", data); + + return result; + } + + public List> listNodesByProcessDefinitionKey( + String processDefinitionKey, Integer processDefinitionVersion) { + + ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey(processDefinitionKey); + + ProcessDefinition processDefinition; + + if (processDefinitionVersion == null) { + // ⭐ 未指定版本 → 查最新版本 + processDefinition = query.latestVersion().singleResult(); + } else { + // ⭐ 指定版本 → 查询对应版本 + processDefinition = query + .processDefinitionVersion(processDefinitionVersion) + .singleResult(); + } + + if (processDefinition == null) { + return Collections.emptyList(); + } + + return getFlowStructureWithExtensions(processDefinition.getId()); + } + + /** + * 根据流程定义ID获取节点信息 + */ + public List> getNodesByProcessDefinitionId(String processDefinitionId) { + return getFlowStructureWithExtensions(processDefinitionId); + } + + /** + * 带 extensionElements 的流程结构解析 + * + * @param processDefinitionId + * @return + */ + public List> getFlowStructureWithExtensions(String processDefinitionId) { + + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); + if (bpmnModel == null) return Collections.emptyList(); + + Process process = bpmnModel.getMainProcess(); + + // 找开始事件 + StartEvent startEvent = process.findFlowElementsOfType(StartEvent.class, false) + .stream().findFirst().orElse(null); + + if (startEvent == null) return Collections.emptyList(); + + List> result = new ArrayList<>(); + Queue queue = new LinkedList<>(); + Set visited = new HashSet<>(); + + queue.add(startEvent); + + while (!queue.isEmpty()) { + FlowNode node = queue.poll(); + if (!visited.add(node.getId())) continue; + + Map info = new LinkedHashMap<>(); + info.put("id", node.getId()); + info.put("name", node.getName()); + info.put("type", node.getClass().getSimpleName()); + + // 后继节点 + List nextNodeIds = node.getOutgoingFlows() + .stream().map(SequenceFlow::getTargetRef).collect(Collectors.toList()); + info.put("next", nextNodeIds); + + // ⭐ 添加扩展属性 extensionElements ⭐ + info.put("extensionElements", parseExtensionElements(node.getExtensionElements())); + + result.add(info); + + // BFS 的推进逻辑 + for (String nextId : nextNodeIds) { + FlowElement nextElement = process.getFlowElement(nextId); + if (nextElement instanceof FlowNode nextNode) { + queue.add(nextNode); + } + } + } + + return result; + } + + // 将 extensionElements 转成可阅读的 map + private Object parseExtensionElements(Map> extMap) { + if (extMap == null || extMap.isEmpty()) return Collections.emptyMap(); + + Map result = new LinkedHashMap<>(); + + extMap.forEach((key, extList) -> { + List> values = new ArrayList<>(); + + for (ExtensionElement ext : extList) { + Map item = new LinkedHashMap<>(); + item.put("name", ext.getName()); + item.put("namespace", ext.getNamespacePrefix()); + item.put("elementText", ext.getElementText()); + item.put("attributes", ext.getAttributes()); + + values.add(item); + } + + result.put(key, values); + }); + + return result; + } + + + + + + + public void deleteAllDeployments() { + repositoryService.createDeploymentQuery() + .list() + .forEach(deployment + -> repositoryService.deleteDeployment(deployment.getId(), true)); + } + + + // 启动流程实例 + public ProcessInstance startByProcessDefinitionKey(String processDefinitionKey) { + return runtimeService.startProcessInstanceByKey(processDefinitionKey); + } + + public ProcessInstance startByProcessDefinitionId(String processDefinitionId, Map variables) { + if (variables == null) { + variables = Collections.emptyMap(); + } + + return runtimeService.startProcessInstanceById(processDefinitionId, variables); + } + + // 获取BPMN模型用于调试和验证 + public BpmnModel getBpmnModel(ProcessDefinitionDTO processDTO) throws JsonProcessingException { + return dto2BpmnConverter.convert(processDTO); + } + + // 验证并返回模型验证错误信息 + public List validateModel(ProcessDefinitionDTO processDTO) throws JsonProcessingException { + BpmnModel bpmnModel = dto2BpmnConverter.convert(processDTO); + ProcessValidator validator = new ProcessValidatorFactory().createDefaultProcessValidator(); + return validator.validate(bpmnModel); + } + + + + public HistoricProcessInstance getLatestProcessInstance(String processDefinitionKey) { + + // 查询最新版本流程定义 + ProcessDefinition latestProcDef = repositoryService + .createProcessDefinitionQuery() + .processDefinitionKey(processDefinitionKey) + .latestVersion() + .singleResult(); + + if (latestProcDef == null) { + return null; + } + + // 优先查正在运行的 + ProcessInstance running = runtimeService.createProcessInstanceQuery() + .processDefinitionId(latestProcDef.getId()) + .orderByStartTime() + .desc() + .listPage(0, 1) + .stream() + .findFirst() + .orElse(null); + + if (running != null) { + return historyService.createHistoricProcessInstanceQuery() + .processInstanceId(running.getId()) + .singleResult(); + } + + // 查最近的历史实例(已结束) + return historyService.createHistoricProcessInstanceQuery() + .processDefinitionId(latestProcDef.getId()) + .orderByProcessInstanceStartTime() + .desc() + .listPage(0, 1) + .stream() + .findFirst() + .orElse(null); + } + + + /** + * 查询流程的所有节点状态 + * + * @param processInstanceId + * @return + */ + public Map getProcessNodeDetail(String processInstanceId) { + return buildProcessNodeDetail(processInstanceId); + } + + public Map getNodeDetailByProcessDefinitionKey(String processDefinitionKey) { + HistoricProcessInstance processInstance = getLatestProcessInstance(processDefinitionKey); + + if (processInstance == null) { + throw new RuntimeException("流程不存在或未运行过: " + processDefinitionKey); + } + + return buildProcessNodeDetail(processInstance.getId()); + } + + /** + * 根据流程实例ID构建流程节点状态(支持 UserTask 任务信息和扩展属性) + */ + private Map buildProcessNodeDetail(String processInstanceId) { + + Map result = new HashMap<>(); + + // 1. 查询流程实例历史信息 + HistoricProcessInstance processInstance = + historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + + if (processInstance == null) { + throw new RuntimeException("流程实例不存在: " + processInstanceId); + } + + String processDefinitionId = processInstance.getProcessDefinitionId(); + + // 2. 获取 BPMN 模型 + BpmnModel model = repositoryService.getBpmnModel(processDefinitionId); + Process process = model.getMainProcess(); + Collection flowElements = process.getFlowElements(); + + // 3. 查询正在执行的节点 + // 判断流程是否正在运行中 + boolean isRunning = runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult() != null; + + List activeIds; + Map activeTaskMap = new HashMap<>(); + if (isRunning) { + activeIds = runtimeService.getActiveActivityIds(processInstanceId); + + // 查询当前活跃任务(UserTask) + List tasks = taskService.createTaskQuery() + .processInstanceId(processInstanceId) + .list(); + activeTaskMap = tasks.stream().collect(Collectors.toMap(org.flowable.task.api.Task::getTaskDefinitionKey, task -> task)); + + } else { + activeIds = Collections.emptyList(); + } + + // 4. 查询已完成节点 + List finished = + historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId) + .finished() + .list(); + + Set finishedIds = finished.stream() + .map(HistoricActivityInstance::getActivityId) + .collect(Collectors.toSet()); + + // 5. 组装节点状态 + List> nodeList = new ArrayList<>(); + + for (FlowElement element : flowElements) { + if (!(element instanceof FlowNode)) continue; + + FlowNode node = (FlowNode) element; + + Map nodeInfo = new HashMap<>(); + nodeInfo.put("id", node.getId()); + nodeInfo.put("name", node.getName()); + nodeInfo.put("type", node.getClass().getSimpleName()); + + // 节点状态 + if (activeIds.contains(node.getId())) { + nodeInfo.put("status", "active"); + + // 如果是 UserTask,返回任务信息 + if (node instanceof UserTask && activeTaskMap.containsKey(node.getId())) { + org.flowable.task.api.Task task = activeTaskMap.get(node.getId()); + nodeInfo.put("taskId", task.getId()); + nodeInfo.put("taskName", task.getName()); + nodeInfo.put("assignee", task.getAssignee()); // 可能为 null + } + } else if (finishedIds.contains(node.getId())) { + nodeInfo.put("status", "finished"); + } else { + nodeInfo.put("status", "pending"); + } + + // 读取扩展属性(executeConfig) + if (node.getExtensionElements() != null && node.getExtensionElements().containsKey(FlowableConfig.EXECUTECONFIG)) { + List elements = node.getExtensionElements().get(FlowableConfig.EXECUTECONFIG); + if (elements != null && !elements.isEmpty()) { + nodeInfo.put("executeConfig", elements.get(0).getElementText()); + } + } + + nodeList.add(nodeInfo); + } + + result.put("processInstanceId", processInstanceId); + result.put("nodes", nodeList); + return result; + } + + public void completeManualTasks(@RequestBody CompleteTaskReq req) { + Task task = taskService.createTaskQuery() + .processInstanceId(req.getProcessInstanceId()) + .taskDefinitionKey(req.getTaskDefinitionKey()) + .singleResult(); + + if (task != null) { + taskService.complete(task.getId(), req.getVariables()); + } else { + throw new RuntimeException("找不到任务!"); + } + + } +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/service/IAsyncTaskRecordService.java b/flowable/src/main/java/com/sdm/flowable/service/IAsyncTaskRecordService.java new file mode 100644 index 00000000..5f61b23d --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/service/IAsyncTaskRecordService.java @@ -0,0 +1,28 @@ +package com.sdm.flowable.service; + +import com.sdm.flowable.dto.req.AsyncCallbackRequest; +import com.sdm.flowable.entity.AsyncTaskRecord; +import com.baomidou.mybatisplus.extension.service.IService; +import org.flowable.engine.delegate.DelegateExecution; + +import java.util.Map; + +/** + *

+ * 异步任务执行记录表 服务类 + *

+ * + * @author author + * @since 2025-11-26 + */ +public interface IAsyncTaskRecordService extends IService { + /** + * 注册异步任务 + */ + String registerAsyncTask(DelegateExecution execution, String receiveTaskId, String handlerType, Map bizParams); + + /** + * 异步回调恢复流程 + */ + void completeAsyncTask(AsyncCallbackRequest request); +} diff --git a/flowable/src/main/java/com/sdm/flowable/service/IProcessNodeParamService.java b/flowable/src/main/java/com/sdm/flowable/service/IProcessNodeParamService.java new file mode 100644 index 00000000..f2949924 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/service/IProcessNodeParamService.java @@ -0,0 +1,19 @@ +package com.sdm.flowable.service; + +import com.sdm.flowable.entity.ProcessNodeParam; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.Map; + +/** + *

+ * 流程节点输入参数表 服务类 + *

+ * + * @author author + * @since 2025-11-25 + */ +public interface IProcessNodeParamService extends IService { + void saveParamByDefinitionId(String processDefinitionId, String nodeId, Map params); + Map getParam(String procInstId, String nodeId); +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/service/impl/AsyncTaskRecordServiceImpl.java b/flowable/src/main/java/com/sdm/flowable/service/impl/AsyncTaskRecordServiceImpl.java new file mode 100644 index 00000000..23dd2d00 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/service/impl/AsyncTaskRecordServiceImpl.java @@ -0,0 +1,71 @@ +package com.sdm.flowable.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.sdm.flowable.dto.req.AsyncCallbackRequest; +import com.sdm.flowable.entity.AsyncTaskRecord; +import com.sdm.flowable.dao.AsyncTaskRecordMapper; +import com.sdm.flowable.service.IAsyncTaskRecordService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.Execution; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.UUID; + +/** + *

+ * 异步任务执行记录表 服务实现类 + *

+ * + * @author author + * @since 2025-11-26 + */ +@Service +public class AsyncTaskRecordServiceImpl extends ServiceImpl implements IAsyncTaskRecordService { + @Autowired + private RuntimeService runtimeService; + + /** + * 注册异步任务 + */ + public String registerAsyncTask(DelegateExecution execution, String receiveTaskId, String handlerType, Map bizParams) { + String asyncTaskId = UUID.randomUUID().toString(); + + AsyncTaskRecord record = new AsyncTaskRecord(); + record.setAsyncTaskId(asyncTaskId); + record.setProcessInstanceId(execution.getProcessInstanceId()); + record.setExecutionId(execution.getId()); + record.setReceiveTaskId(receiveTaskId); + record.setHandlerType(handlerType); + record.setRequestJson(JSON.toJSONString(bizParams)); + record.setStatus("INIT"); + this.save( record); + return asyncTaskId; + } + + /** + * 异步回调恢复流程 + */ + public void completeAsyncTask(AsyncCallbackRequest request) { + AsyncTaskRecord record = this.lambdaQuery().eq(AsyncTaskRecord::getAsyncTaskId, request.getAsyncTaskId()).one(); + if (record == null) { + throw new RuntimeException("AsyncTask not found: " + request); + } + record.setStatus("SUCCESS"); + record.setResultJson(request.getResultJson()); + this.updateById(record); + Execution exec = runtimeService.createExecutionQuery() + .processInstanceId(record.getProcessInstanceId()) + .activityId(record.getReceiveTaskId()) + .singleResult(); + + if (exec == null) { + throw new RuntimeException("ReceiveTask 已不在等待状态"); + } + + runtimeService.trigger(exec.getId()); + } +} diff --git a/flowable/src/main/java/com/sdm/flowable/service/impl/ProcessNodeParamServiceImpl.java b/flowable/src/main/java/com/sdm/flowable/service/impl/ProcessNodeParamServiceImpl.java new file mode 100644 index 00000000..64912960 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/service/impl/ProcessNodeParamServiceImpl.java @@ -0,0 +1,78 @@ +package com.sdm.flowable.service.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sdm.flowable.entity.ProcessNodeParam; +import com.sdm.flowable.dao.ProcessNodeParamMapper; +import com.sdm.flowable.service.IProcessNodeParamService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.flowable.engine.RuntimeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 流程节点输入参数表 服务实现类 + *

+ * + * @author author + * @since 2025-11-25 + */ +@Service +public class ProcessNodeParamServiceImpl extends ServiceImpl implements IProcessNodeParamService { + + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RuntimeService runtimeService; + + // 保存节点输入参数(按流程定义ID保存,用于参数模板) + public void saveParamByDefinitionId(String processDefinitionId, String nodeId, Map params) { + ProcessNodeParam param = new ProcessNodeParam(); + param.setProcessDefinitionId(processDefinitionId); + param.setNodeId(nodeId); + try { + param.setParamJson(objectMapper.writeValueAsString(params)); + } catch (JsonProcessingException e) { + throw new RuntimeException("参数序列化失败", e); + } + // 存在则更新,不存在则插入 + ProcessNodeParam existing = this.lambdaQuery().eq(ProcessNodeParam::getProcessDefinitionId, processDefinitionId).eq(ProcessNodeParam::getNodeId, nodeId).one(); + if (existing != null) { + param.setId(existing.getId()); + this.updateById(param); + } else { + this.save(param); + } + } + + // 查询节点输入参数(流程执行时调用) + public Map getParam(String procInstId, String nodeId) { + // 获取流程实例对应的流程定义ID + String processDefinitionId = runtimeService.createProcessInstanceQuery() + .processInstanceId(procInstId) + .singleResult() + .getProcessDefinitionId(); + + ProcessNodeParam param = this.lambdaQuery().eq(ProcessNodeParam::getProcessDefinitionId, processDefinitionId) + .eq(ProcessNodeParam::getNodeId, nodeId) + .one(); + + if (param == null) { + // 当未配置参数时不抛出异常,而是返回空Map + return new HashMap<>(); + } + try { + return objectMapper.readValue(param.getParamJson(), new TypeReference>() {}); + } catch (JsonProcessingException e) { + throw new RuntimeException("参数反序列化失败", e); + } + } +} \ No newline at end of file diff --git a/flowable/src/main/java/com/sdm/flowable/util/Dto2BpmnConverter.java b/flowable/src/main/java/com/sdm/flowable/util/Dto2BpmnConverter.java new file mode 100644 index 00000000..f73aa106 --- /dev/null +++ b/flowable/src/main/java/com/sdm/flowable/util/Dto2BpmnConverter.java @@ -0,0 +1,378 @@ +package com.sdm.flowable.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sdm.flowable.dto.FlowElementDTO; +import com.sdm.flowable.dto.ProcessDefinitionDTO; +import com.sdm.flowable.config.executeConfig.BaseExecuteConfig; +import com.sdm.flowable.constants.FlowableConfig; +import com.sdm.flowable.dto.FlowElementDTO; +import com.sdm.flowable.dto.ProcessDefinitionDTO; +import com.sdm.flowable.enums.FlowElementTypeEnums; +import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * DTO → Flowable BpmnModel 映射工具类(核心) + */ +@Component +public class Dto2BpmnConverter { + + @Autowired + private ObjectMapper objectMapper; + + /** + * 核心映射方法 + */ + public BpmnModel convert(ProcessDefinitionDTO dto) throws JsonProcessingException { + // 1. 初始化 Flowable 顶层对象 + BpmnModel bpmnModel = new BpmnModel(); + Process process = new Process(); + process.setId(dto.getProcess().getId()); // 流程ID=DTO的process.id + process.setName(dto.getProcess().getName()); // 流程名称=DTO的process.name + bpmnModel.addProcess(process); + + // 2. 分离 DTO 中的「节点」和「连线」 + List allElements = dto.getFlowElements(); + List nodeDtos = allElements.stream() + .filter(e -> !FlowElementTypeEnums.SEQUENCEFLOW.getType().equals(e.getType())) + .collect(Collectors.toList()); + List flowDtos = allElements.stream() + .filter(e -> FlowElementTypeEnums.SEQUENCEFLOW.getType().equals(e.getType())) + .collect(Collectors.toList()); + + // 3. 存储异步任务映射关系(原节点ID → wait节点ID) + Map asyncTaskMap = new HashMap<>(); // 异步任务映射(原节点→wait节点) + + // 4. 存储并行网关映射关系(原节点ID → 网关ID) + Map splitGatewayMap = new HashMap<>(); // 拆分网关(原拆分节点→拆分网关) + Map joinGatewayMap = new HashMap<>(); // 汇总网关(原汇总节点→汇总网关) + + // 5. 先创建所有节点(实际节点+等待任务节点+网关节点) + for (FlowElementDTO nodeDto : nodeDtos) { + // 处理异步任务,创建等待节点,放在穿件实际节点之前是为了构造asyncTaskMap,后面createActualNode的时候才能设置回调等待节点 + handleAsyncTasks(process, nodeDto, asyncTaskMap); + // 创建实际节点 + createActualNode(process, nodeDto,asyncTaskMap); + // 处理并行网关,创建拆分和汇聚节点 + addRequiredGateways(process, nodeDto, flowDtos, joinGatewayMap, splitGatewayMap); + } + + // 6. 创建连线 + createConnections(process, flowDtos, asyncTaskMap, joinGatewayMap, splitGatewayMap); + + return bpmnModel; + } + + /** + * 处理异步任务节点 + */ + private void handleAsyncTasks(Process process, FlowElementDTO nodeDto, Map asyncTaskMap) { + // 检查节点是否为服务任务或用户任务且标记为异步回调 + if ((FlowElementTypeEnums.SERVICETASK.getType().equals(nodeDto.getType()) || + FlowElementTypeEnums.USERTASK.getType().equals(nodeDto.getType())) && + nodeDto.getExtensionElements() != null && + nodeDto.getExtensionElements().getExecuteConfig() != null && + nodeDto.getExtensionElements().getExecuteConfig().isAsyncCallback()) { + + // 创建接收任务节点 + String originalNodeId = nodeDto.getId(); + String waitNodeId = originalNodeId + "_wait"; + ReceiveTask receiveTask = new ReceiveTask(); + receiveTask.setId(waitNodeId); + receiveTask.setName(nodeDto.getName() + "等待结果"); + process.addFlowElement(receiveTask); + + // 记录映射关系 + asyncTaskMap.put(originalNodeId, waitNodeId); + } + } + + /** + * 添加必要的网关(并行拆分网关和并行汇聚网关) + */ + private void addRequiredGateways(Process process, FlowElementDTO nodeDto, List flowDtos, + Map joinGatewayMap, Map splitGatewayMap) { + String nodeId = nodeDto.getId(); + + // 计算入度和出度 + long incomingCount = flowDtos.stream().filter(f -> f.getTargetRef().equals(nodeId)).count(); + long outgoingCount = flowDtos.stream().filter(f -> f.getSourceRef().equals(nodeId)).count(); + + // 检查是否需要添加汇聚网关(入度>1) + if (incomingCount > 1) { + // 如果入度>1,则在节点前插入汇聚网关 + String joinGatewayId = "join_gw_" + nodeId; + ParallelGateway joinGateway = new ParallelGateway(); + joinGateway.setId(joinGatewayId); + joinGateway.setName("并行汇聚-" + nodeDto.getName()); + process.addFlowElement(joinGateway); + joinGatewayMap.put(nodeId, joinGatewayId); + } + + // 检查是否需要添加拆分网关(出度>1) + if (outgoingCount > 1) { + // 如果出度>1,则在节点后插入拆分网关 + String splitGatewayId = "split_gw_" + nodeId; + ParallelGateway splitGateway = new ParallelGateway(); + splitGateway.setId(splitGatewayId); + splitGateway.setName("并行拆分-" + nodeDto.getName()); + process.addFlowElement(splitGateway); + splitGatewayMap.put(nodeId, splitGatewayId); + } + } + + /** + * 全新的连线构造逻辑 + */ + private void createConnections(Process process, + List flowDtos, + Map asyncTaskMap, + Map joinGatewayMap, + Map splitGatewayMap) { + + // ==================================================================================== + // ① 第一阶段:先把所有 DTO 原始连线直接画出来(不考虑网关和等待节点) + // ==================================================================================== + createInitialConnections(process, flowDtos); + + // ==================================================================================== + // ② 第二阶段:网关处理(删除原始连线 → 使用网关替代) + // ==================================================================================== + handleGatewayConnections(process, flowDtos, joinGatewayMap, splitGatewayMap); + + // ==================================================================================== + // ③ 第三阶段:处理异步任务(等待节点) + // 原逻辑:原节点 → wait → 原本下游 + // ==================================================================================== + handleAsyncTaskConnections(process, asyncTaskMap); + } + + /** + * 第一阶段:创建初始连接 + * 遍历所有流程元素DTO,根据源引用和目标引用创建基本的顺序流连接 + */ + private void createInitialConnections(Process process, List flowDtos) { + for (FlowElementDTO flowDto : flowDtos) { + String source = flowDto.getSourceRef(); + String target = flowDto.getTargetRef(); + + SequenceFlow flow = createSequenceFlow(source, target, flowDto.getName()); + process.addFlowElement(flow); + } + } + + /** + * 第二阶段:处理网关连接 + * 包括删除需要被网关替代的原始连线以及重建涉及汇聚网关和拆分网关的连接关系 + */ + private void handleGatewayConnections(Process process, + List flowDtos, + Map joinGatewayMap, + Map splitGatewayMap) { + // Step 2.1 删除所有不该存在的连线(因为网关将替代) + List toRemove = new ArrayList<>(); + + for (FlowElement ele : process.getFlowElements()) { + if (ele instanceof SequenceFlow) { + SequenceFlow sf = (SequenceFlow) ele; + + // 目标节点有汇聚网关 → 删除原连线 + if (joinGatewayMap.containsKey(sf.getTargetRef())) { + toRemove.add(sf); + } + + // 源节点有拆分网关 → 删除原连线 + if (splitGatewayMap.containsKey(sf.getSourceRef())) { + toRemove.add(sf); + } + } + } + toRemove.forEach(e -> process.removeFlowElement(e.getId())); + + + // Step 2.2 重建汇聚网关连线(incoming:所有入线 → joinGW → 原节点) + for (String nodeId : joinGatewayMap.keySet()) { + + String joinGW = joinGatewayMap.get(nodeId); + + // 1. 所有"指向 nodeId 的原始入度" → joinGW + List incomingSources = flowDtos.stream() + .filter(f -> f.getTargetRef().equals(nodeId)) + .map(FlowElementDTO::getSourceRef) + .toList(); + + for (String src : incomingSources) { + process.addFlowElement(createSequenceFlow(src, joinGW, null)); + } + + // 2. joinGW → nodeId + process.addFlowElement(createSequenceFlow(joinGW, nodeId, null)); + } + + + // Step 2.3 重建拆分网关连线(node → splitGW → 所有原本出度) + for (String nodeId : splitGatewayMap.keySet()) { + + String splitGW = splitGatewayMap.get(nodeId); + + // 1. nodeId → splitGW + process.addFlowElement(createSequenceFlow(nodeId, splitGW, null)); + + // 2. splitGW → 所有 target + List outgoingTargets = flowDtos.stream() + .filter(f -> f.getSourceRef().equals(nodeId)) + .map(FlowElementDTO::getTargetRef) + .toList(); + + for (String target : outgoingTargets) { + process.addFlowElement(createSequenceFlow(splitGW, target, null)); + } + } + } + + /** + * 第三阶段:处理异步任务连接 + * 针对已标记为异步回调的任务节点,将其连接重构为'原节点 → 等待节点 → 原目标节点'的模式 + */ + private void handleAsyncTaskConnections(Process process, Map asyncTaskMap) { + for (String originalNodeId : asyncTaskMap.keySet()) { + + String waitNodeId = asyncTaskMap.get(originalNodeId); + + // Step 3.1 找出所有"原节点 → target"的连线,并删除 + List removeLines = new ArrayList<>(); + + List targets = new ArrayList<>(); + + for (FlowElement ele : process.getFlowElements()) { + if (ele instanceof SequenceFlow) { + SequenceFlow sf = (SequenceFlow) ele; + if (sf.getSourceRef().equals(originalNodeId)) { + targets.add(sf.getTargetRef()); + removeLines.add(sf); + } + } + } + + removeLines.forEach(f -> process.removeFlowElement(f.getId())); + + + // Step 3.2 添加:original → wait + process.addFlowElement(createSequenceFlow(originalNodeId, waitNodeId, null)); + + // Step 3.3 添加:wait → 原目标 + for (String target : targets) { + process.addFlowElement(createSequenceFlow(waitNodeId, target, null)); + } + } + } + + /** + * 创建实际的流程节点 + */ + private void createActualNode(Process process, FlowElementDTO nodeDto, Map asyncTaskMap) throws JsonProcessingException { + FlowElementTypeEnums elementType = FlowElementTypeEnums.fromString(nodeDto.getType()); + + switch (elementType) { + case STARTEVENT: + // 开始事件:直接映射 + StartEvent startEvent = new StartEvent(); + startEvent.setId(nodeDto.getId()); + startEvent.setName(nodeDto.getName()); + process.addFlowElement(startEvent); + break; + + case ENDEVENT: + // 结束事件:直接映射 + EndEvent endEvent = new EndEvent(); + endEvent.setId(nodeDto.getId()); + endEvent.setName(nodeDto.getName()); + process.addFlowElement(endEvent); + break; + + case USERTASK: + // 用户任务:映射为 Flowable UserTask + UserTask userTask = new UserTask(); + userTask.setId(nodeDto.getId()); + userTask.setName(nodeDto.getName()); + + // 绑定控制参数(和 ServiceTask 类似) + BaseExecuteConfig userTaskExecuteConfig = nodeDto.getExtensionElements().getExecuteConfig(); + // 设置异步回调节点ID + userTaskExecuteConfig.setCallbackNodeId(asyncTaskMap.getOrDefault(nodeDto.getId(), null)); + if (userTaskExecuteConfig != null) { + String configJson = objectMapper.writeValueAsString(userTaskExecuteConfig); + ExtensionElement extensionElement = createFlowableElement( + FlowableConfig.EXECUTECONFIG, configJson); + userTask.getExtensionElements() + .computeIfAbsent(FlowableConfig.EXECUTECONFIG, k -> new ArrayList<>()) + .add(extensionElement); + } + // 设置用户任务的属性,使其可以被任何人处理 + // 不设置 assignee 或 candidateUsers,这样任何人都可以处理任务 + + // 可选:绑定 TaskListener,在任务完成时触发逻辑 + userTask.getTaskListeners(); + + process.addFlowElement(userTask); + break; + + case SERVICETASK: + // 服务任务:映射为 Flowable ServiceTask,绑定自定义执行器 + ServiceTask serviceTask = new ServiceTask(); + serviceTask.setId(nodeDto.getId()); + serviceTask.setName(nodeDto.getName()); + // 绑定执行器(Bean名称:customTaskExecutor) + 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) { + String configJson = objectMapper.writeValueAsString(serviceTaskExecuteConfig); + ExtensionElement extensionElement = createFlowableElement(FlowableConfig.EXECUTECONFIG, configJson); + serviceTask.getExtensionElements().computeIfAbsent(FlowableConfig.EXECUTECONFIG, k -> new ArrayList<>()) + .add(extensionElement); + } + + process.addFlowElement(serviceTask); + break; + + default: + // 对于未支持的类型,可以选择抛出异常或者忽略 + throw new IllegalArgumentException("Unsupported element type: " + nodeDto.getType()); + } + } + + private ExtensionElement createFlowableElement(String name, String value) { + ExtensionElement element = new ExtensionElement(); + element.setName(name); + element.setNamespace("http://flowable.org/bpmn"); + element.setNamespacePrefix("flowable"); + element.setElementText(value); + return element; + } + + /** + * 辅助方法:创建 Flowable 连线(SequenceFlow) + */ + private SequenceFlow createSequenceFlow(String sourceRef, String targetRef, String name) { + SequenceFlow flow = new SequenceFlow(); + // 确保生成的ID符合NCName规范(以字母开头) + flow.setId("s" + UUID.randomUUID().toString().replace("-", "")); + flow.setSourceRef(sourceRef); // 源节点ID + flow.setTargetRef(targetRef); // 目标节点ID + if (name != null) { + flow.setName(name); + } + return flow; + } +} \ No newline at end of file diff --git a/flowable/src/main/resources/application.yml b/flowable/src/main/resources/application.yml new file mode 100644 index 00000000..5cd33827 --- /dev/null +++ b/flowable/src/main/resources/application.yml @@ -0,0 +1,36 @@ +server: + port: 7106 +spring: + application: + name: flowable + datasource: + url: jdbc:mysql://192.168.65.161:3306/flowable?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai + username: root + password: mysql + driver-class-name: com.mysql.cj.jdbc.Driver + flowable: + # ????????? + database-schema-update: true + # ??????JOB + async-executor-activate: true + cloud: + nacos: + discovery: + server-addr: 192.168.65.161:8848 + group: LOCAL_GROUP + enabled: true + +logging: + level: + org: + flowable: INFO + +mybatis-plus: + mapper-locations: classpath*:/mapper/**/*.xml + type-aliases-package: com.sdm.flowable.model.entity + configuration: + map-underscore-to-camel-case: true + global-config: + db-config: + id-type: auto + diff --git a/flowable/src/main/resources/flow.json b/flowable/src/main/resources/flow.json new file mode 100644 index 00000000..98b64ee9 --- /dev/null +++ b/flowable/src/main/resources/flow.json @@ -0,0 +1,160 @@ +{ + "process": { + "id": "auto_process_001", + "name": "网关串并行+人工节点+异步回调混合流程" + }, + "flowElements": [ + { + "id": "start", + "type": "startEvent", + "name": "第1步-流程启动", + "outgoingFlows": ["flow1"] + }, + { + "id": "task_script", + "type": "serviceTask", + "name": "第2步-HPC异步执行", + "incomingFlows": ["flow1"], + "outgoingFlows": ["flow2"], + "extensionElements": { + "executeConfig": { + "executeType": "HPC", + "asyncCallback": true + } + } + }, + { + "id": "task_http", + "type": "serviceTask", + "name": "第3步-HTTP请求", + "incomingFlows": ["flow2"], + "outgoingFlows": ["flow3", "flow4"], + "extensionElements": { + "executeConfig": { + "executeType": "http" + } + } + }, + { + "id": "task_backup", + "type": "serviceTask", + "name": "第3步后-并行分支1-数据备份", + "incomingFlows": ["flow3"], + "outgoingFlows": ["flow5"], + "extensionElements": { + "executeConfig": { + "executeType": "http" + } + } + }, + { + "id": "task_log", + "type": "serviceTask", + "name": "第3步后-并行第分支2-日志记录", + "incomingFlows": ["flow4"], + "outgoingFlows": ["flow6"], + "extensionElements": { + "executeConfig": { + "executeType": "http" + } + } + }, + { + "id": "task_parallel_join", + "type": "serviceTask", + "name": "第4步-并行汇总", + "incomingFlows": ["flow5", "flow6"], + "outgoingFlows": ["flow7"], + "extensionElements": { + "executeConfig": { + "executeType": "dataProcess" + } + } + }, + { + "id": "task_manual_approval", + "type": "userTask", + "name": "第5步-人工操作节点-数据确认", + "incomingFlows": ["flow7"], + "outgoingFlows": ["flow8"], + "extensionElements": { + "executeConfig": { + "executeType": "manual", + "description": "人工确认数据处理" + } + } + }, + { + "id": "task_data_process", + "type": "serviceTask", + "name": "第6步-数据处理", + "incomingFlows": ["flow8"], + "outgoingFlows": ["flow9"], + "extensionElements": { + "executeConfig": { + "executeType": "dataProcess" + } + } + }, + { + "id": "end", + "type": "endEvent", + "name": "第7步-流程结束", + "incomingFlows": ["flow9"] + }, + { + "id": "flow1", + "type": "sequenceFlow", + "sourceRef": "start", + "targetRef": "task_script" + }, + { + "id": "flow2", + "type": "sequenceFlow", + "sourceRef": "task_script", + "targetRef": "task_http" + }, + { + "id": "flow3", + "type": "sequenceFlow", + "sourceRef": "task_http", + "targetRef": "task_backup" + }, + { + "id": "flow4", + "type": "sequenceFlow", + "sourceRef": "task_http", + "targetRef": "task_log" + }, + { + "id": "flow5", + "type": "sequenceFlow", + "sourceRef": "task_backup", + "targetRef": "task_parallel_join" + }, + { + "id": "flow6", + "type": "sequenceFlow", + "sourceRef": "task_log", + "targetRef": "task_parallel_join" + }, + { + "id": "flow7", + "type": "sequenceFlow", + "sourceRef": "task_parallel_join", + "targetRef": "task_manual_approval" + }, + { + "id": "flow8", + "type": "sequenceFlow", + "sourceRef": "task_manual_approval", + "targetRef": "task_data_process" + }, + { + "id": "flow9", + "type": "sequenceFlow", + "sourceRef": "task_data_process", + "targetRef": "end" + } + ] +} diff --git a/flowable/src/main/resources/mapper/AsyncTaskRecordMapper.xml b/flowable/src/main/resources/mapper/AsyncTaskRecordMapper.xml new file mode 100644 index 00000000..765d27b9 --- /dev/null +++ b/flowable/src/main/resources/mapper/AsyncTaskRecordMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/flowable/src/main/resources/mapper/ProcessNodeParamMapper.xml b/flowable/src/main/resources/mapper/ProcessNodeParamMapper.xml new file mode 100644 index 00000000..fbc20076 --- /dev/null +++ b/flowable/src/main/resources/mapper/ProcessNodeParamMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/flowable/src/test/java/com/sdm/flowable/FlowableApplicationTests.java b/flowable/src/test/java/com/sdm/flowable/FlowableApplicationTests.java new file mode 100644 index 00000000..47bac115 --- /dev/null +++ b/flowable/src/test/java/com/sdm/flowable/FlowableApplicationTests.java @@ -0,0 +1,13 @@ +package com.sdm.flowable; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class FlowableApplicationTests { + + @Test + void contextLoads() { + } + +}