diff --git a/.drone.yml b/.drone.yml index d608371..b4599fa 100644 --- a/.drone.yml +++ b/.drone.yml @@ -27,11 +27,9 @@ steps: from_secret: docker_password REGISTRY: from_secret: registry - REGISTRY_NAMESPACE: - from_secret: registry_namespace commands: - sed -i 's/$REGISTRY/'"$REGISTRY"'/' deployment.yml - - sed -i 's/$REGISTRY_NAMESPACE/'"$REGISTRY_NAMESPACE"'/' deployment.yml + - sed -i 's/$REGISTRY_NAMESPACE/'"metis"'/' deployment.yml - sed -i 's/$DRONE_COMMIT/${DRONE_COMMIT}/' deployment.yml - sed -i 's/$DRONE_REPO_NAME/${DRONE_REPO_NAME}/' deployment.yml - echo $DOCKER_PASSWORD | docker login $REGISTRY --username $DOCKER_USERNAME --password-stdin @@ -47,6 +45,35 @@ steps: - kubectl apply -f deployment.yml -n metis --kubeconfig=/app/config/base-taishan-kubectl.yml + - name: notify + image: 10.7.127.190:38080/plugins/webhook:latest + environment: + NOTIFY_WX_URL: + from_secret: notify_wx_url + when: + status: [ success,failure ] + settings: + urls: + from_secret: notify_wx_url + content_type: application/json + template: | + { + "msgtype": "markdown", + "markdown": { + "content": "{{#success build.status}}✅ 构建成功{{else}}❌ 构建失败{{/success}} + >**构建编号**: #{{build.number}} + >**构建状态**: {{build.status}} + >**代码分支**: {{build.branch}} + >**提交哈希**: {{build.commit}} + >**提交作者**: {{build.author}} + >**持续时间**: {{build.duration}}秒 + >**提交信息**: {{build.message}} + >[查看构建详情]({{build.link}}) + >{{^success build.status}}[查看失败日志]({{build.link}}/logs){{/success}}" + } + } + + volumes: - name: config host: diff --git a/Dockerfile b/Dockerfile index 8da31a9..2d4faca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,6 @@ WORKDIR /app # 定义时区参数并设置时区 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -COPY ./target/metis-1.0.0-SNAPSHOT.jar /app/metis.jar +COPY ./metis-applicant/target/metis-applicant-1.0.0-SNAPSHOT.jar /app/metis.jar RUN chmod 755 -R /app/ CMD java -jar /app/metis.jar -Xms256m -Xmx512m --spring.profiles.active=${PROFILES} --server.port=${SERVICE_PORTS} diff --git a/metis-applicant/pom.xml b/metis-applicant/pom.xml new file mode 100644 index 0000000..6900ad5 --- /dev/null +++ b/metis-applicant/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + com.metis + metis + 1.0.0-SNAPSHOT + + + metis-applicant + + + 17 + 17 + UTF-8 + + + + + com.metis + metis-starter + 1.0.0-SNAPSHOT + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + 4.4.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + org.projectlombok + lombok + + + org.mapstruct + mapstruct-processor + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + UTF-8 + + -parameters + --add-opens + java.base/java.lang=ALL-UNNAMED + + + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/metis/MetisApplication.java b/metis-applicant/src/main/java/com/metisapp/MetisApplication.java similarity index 93% rename from src/main/java/com/metis/MetisApplication.java rename to metis-applicant/src/main/java/com/metisapp/MetisApplication.java index b9dc604..5feb08a 100644 --- a/src/main/java/com/metis/MetisApplication.java +++ b/metis-applicant/src/main/java/com/metisapp/MetisApplication.java @@ -1,4 +1,4 @@ -package com.metis; +package com.metisapp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/com/metis/config/SwaggerConfig.java b/metis-applicant/src/main/java/com/metisapp/config/SwaggerConfig.java similarity index 94% rename from src/main/java/com/metis/config/SwaggerConfig.java rename to metis-applicant/src/main/java/com/metisapp/config/SwaggerConfig.java index 744fe14..77feba0 100644 --- a/src/main/java/com/metis/config/SwaggerConfig.java +++ b/metis-applicant/src/main/java/com/metisapp/config/SwaggerConfig.java @@ -1,7 +1,6 @@ -package com.metis.config; +package com.metisapp.config; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import org.springframework.context.annotation.Bean; diff --git a/metis-applicant/src/main/java/com/metisapp/controller/TestController.java b/metis-applicant/src/main/java/com/metisapp/controller/TestController.java new file mode 100644 index 0000000..be0f467 --- /dev/null +++ b/metis-applicant/src/main/java/com/metisapp/controller/TestController.java @@ -0,0 +1,38 @@ +package com.metisapp.controller; + +import com.metis.domain.bo.BuildApp; +import com.metis.engine.AppFlowEngineRunnerService; +import com.metis.runner.FlowRunningContext; +import com.metis.runner.RunnerResult; +import com.metis.validator.ValidatorService; +import com.metis.result.Result; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/test") +@RequiredArgsConstructor +public class TestController { + + + private final ValidatorService validatorService; + private final AppFlowEngineRunnerService engineRunnerService; + + @PostMapping + public Result test(@RequestBody BuildApp app) { + validatorService.validate(app); + return Result.ok("测试成功"); + } + + + @PostMapping("/run") + public Result run(@RequestBody FlowRunningContext context) { + RunnerResult running = engineRunnerService.running(context); + return Result.ok(running); + } + + +} diff --git a/metis-applicant/src/main/java/com/metisapp/custom/CustomTestConfig.java b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestConfig.java new file mode 100644 index 0000000..b7028c1 --- /dev/null +++ b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestConfig.java @@ -0,0 +1,10 @@ +package com.metisapp.custom; + +import com.metis.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class CustomTestConfig extends NodeConfig { +} diff --git a/metis-applicant/src/main/java/com/metisapp/custom/CustomTestRunner.java b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestRunner.java new file mode 100644 index 0000000..d3ec3be --- /dev/null +++ b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestRunner.java @@ -0,0 +1,27 @@ +package com.metisapp.custom; + +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.runner.CustomNodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class CustomTestRunner implements CustomNodeRunner { + + @Override + public String getCustomNodeType() { + return "test"; + } + + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + log.info("自定义节点测试"); + return RunningResult.buildResult(); + } +} diff --git a/metis-applicant/src/main/java/com/metisapp/custom/CustomTestValidator.java b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestValidator.java new file mode 100644 index 0000000..60ce8b7 --- /dev/null +++ b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestValidator.java @@ -0,0 +1,32 @@ +package com.metisapp.custom; + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.validator.CustomNodeValidator; +import com.metis.validator.ValidatorResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class CustomTestValidator implements CustomNodeValidator { + + @Override + public String getCustomNodeType() { + return "test"; + } + + @Override + public ValidatorResult validateValue(Node node) { + CustomTestConfig config = node.getConfig(); + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + CustomTestConfig config = node.getConfig(); + return ValidatorResult.valid(); + } +} diff --git a/src/main/resources/application-dev.yml b/metis-applicant/src/main/resources/application-dev.yml similarity index 100% rename from src/main/resources/application-dev.yml rename to metis-applicant/src/main/resources/application-dev.yml diff --git a/src/main/resources/application-test.yml b/metis-applicant/src/main/resources/application-test.yml similarity index 100% rename from src/main/resources/application-test.yml rename to metis-applicant/src/main/resources/application-test.yml diff --git a/src/main/resources/application.yml b/metis-applicant/src/main/resources/application.yml similarity index 91% rename from src/main/resources/application.yml rename to metis-applicant/src/main/resources/application.yml index 57c0935..d183ce5 100644 --- a/src/main/resources/application.yml +++ b/metis-applicant/src/main/resources/application.yml @@ -34,10 +34,6 @@ springdoc: swagger-ui: tags-sorter: alpha group-configs: - - group: bis - display-name: "业务接口文档" - paths-to-match: '/**' - packages-to-scan: org.shi9.module.bis - group: system display-name: "系统接口文档" paths-to-match: '/**' diff --git a/metis-applicant/src/main/resources/json/run.json b/metis-applicant/src/main/resources/json/run.json new file mode 100644 index 0000000..c7e457d --- /dev/null +++ b/metis-applicant/src/main/resources/json/run.json @@ -0,0 +1,7 @@ +{ + "appId": "1909636986931470336", + "userId": 1, + "custom": { + "context": "测试内容!" + } +} \ No newline at end of file diff --git a/metis-applicant/src/main/resources/json/test.json b/metis-applicant/src/main/resources/json/test.json new file mode 100644 index 0000000..05521ec --- /dev/null +++ b/metis-applicant/src/main/resources/json/test.json @@ -0,0 +1,567 @@ +{ + "id": 0, + "name": "测试流程", + "description": "测试流程", + "graph": { + "nodes": [ + { + "id": "5", + "type": "start", + "initialized": false, + "position": { + "x": -81.81250000000003, + "y": 275.49609375 + }, + "data": { + "label": "开始", + "icon": "SuitcaseLine", + "toolbarPosition": "right", + "description": "开始述描述", + "config": { + "parent": "234" + }, + "handles": [ + { + "id": "51", + "position": "right", + "type": "source", + "connectable": true + } + ], + "item": { + "id": "2", + "type": "run", + "labelType": "knowledge", + "label": "知识检索" + } + }, + "width": 200, + "height": 40 + }, + { + "id": "6", + "type": "end", + "initialized": false, + "position": { + "x": 1281.582055572882, + "y": 236.2912067630247 + }, + "data": { + "label": "结束", + "toolbarPosition": "right", + "handles": [ + { + "id": "61", + "position": "left", + "type": "target", + "connectable": true + } + ] + }, + "width": 200, + "height": 40 + }, + { + "id": "188", + "type": "custom", + "customType": "test", + "initialized": false, + "position": { + "x": 265.87532955148635, + "y": 71.40983063296031 + }, + "data": { + "label": "llm1", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "11", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "45", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "850", + "type": "llm", + "initialized": false, + "position": { + "x": 269.7896091295129, + "y": 253.80570624004747 + }, + "data": { + "label": "llm2", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "43", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "57", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "979", + "type": "llm", + "initialized": false, + "position": { + "x": 257.0893454883237, + "y": 360 + }, + "data": { + "label": "llm3", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "40", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "46", + "position": "right", + "type": "source", + "connectable": true + } + ], + "item": { + "id": "1", + "type": "run", + "labelType": "llm", + "label": "llm" + } + }, + "width": 200, + "height": null + }, + { + "id": "818", + "type": "questionClassifier", + "initialized": false, + "position": { + "x": 247.56414775743187, + "y": 467.44099824508874 + }, + "data": { + "label": "知识检索条件", + "icon": "", + "description": "知识检索描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "knowledge" + }, + "handles": [ + { + "id": "86", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "11", + "position": "right", + "type": "source", + "connectable": true + } + ], + "item": { + "id": "1", + "type": "run", + "labelType": "llm", + "label": "llm" + } + }, + "width": 200, + "height": null + }, + { + "id": "288", + "type": "llm", + "initialized": false, + "position": { + "x": 545.6891477574318, + "y": 431.4042601585389 + }, + "data": { + "label": "llm5", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "27", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "90", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "873", + "type": "llm", + "initialized": false, + "position": { + "x": 561.0654659633774, + "y": 547.6673703535477 + }, + "data": { + "label": "llm6", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "31", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "51", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "244", + "type": "llm", + "initialized": false, + "position": { + "x": 543.1540601095024, + "y": 653.309197614774 + }, + "data": { + "label": "llm7", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "33", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "13", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "308", + "type": "llm", + "initialized": false, + "position": { + "x": 588.7086470279171, + "y": 318.8609378786052 + }, + "data": { + "label": "llm4", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "17", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "73", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + } + ], + "edges": [ + { + "id": "188", + "type": "default", + "source": "5", + "target": "188", + "sourceHandle": "51", + "targetHandle": "11", + "data": {}, + "label": "", + "animated": true, + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 262.70866773530213, + "targetY": 98.2431395846395 + }, + { + "id": "vueflow__edge-18845-661", + "type": "default", + "source": "188", + "target": "6", + "sourceHandle": "45", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 472.20841119628, + "sourceY": 98.2431395846395, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "850", + "type": "default", + "source": "5", + "target": "850", + "sourceHandle": "51", + "targetHandle": "43", + "data": {}, + "label": "", + "animated": true, + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 266.62293379655364, + "targetY": 280.6389855757115 + }, + { + "id": "vueflow__edge-85057-661", + "type": "default", + "source": "850", + "target": "6", + "sourceHandle": "57", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 476.1223496202179, + "sourceY": 280.6389855757115, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "979", + "type": "default", + "source": "5", + "target": "979", + "sourceHandle": "51", + "targetHandle": "40", + "data": {}, + "label": "", + "animated": true, + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 253.92267015536444, + "targetY": 386.83332778318527 + }, + { + "id": "818", + "type": "default", + "source": "5", + "target": "818", + "sourceHandle": "51", + "targetHandle": "86", + "data": {}, + "label": "", + "animated": true, + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 244.3974724244726, + "targetY": 494.2742291332315 + }, + { + "id": "288", + "type": "default", + "source": "818", + "target": "288", + "sourceHandle": "11", + "targetHandle": "27", + "data": {}, + "label": "", + "animated": true, + "sourceX": 453.89688824813686, + "sourceY": 494.2742291332315, + "targetX": 542.5224239769512, + "targetY": 458.2374910466816 + }, + { + "id": "873", + "type": "default", + "source": "818", + "target": "873", + "sourceHandle": "11", + "targetHandle": "31", + "data": {}, + "label": "", + "animated": true, + "sourceX": 453.89688824813686, + "sourceY": 494.2742291332315, + "targetX": 557.8988921284383, + "targetY": 574.5006486940537 + }, + { + "id": "244", + "type": "default", + "source": "818", + "target": "244", + "sourceHandle": "11", + "targetHandle": "33", + "data": {}, + "label": "", + "animated": true, + "sourceX": 453.89688824813686, + "sourceY": 494.2742291332315, + "targetX": 539.9875200651626, + "targetY": 680.1424930622838 + }, + { + "id": "vueflow__edge-28890-661", + "type": "default", + "source": "288", + "target": "6", + "sourceHandle": "90", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 752.0218882481367, + "sourceY": 458.2374910466816, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "vueflow__edge-87351-661", + "type": "default", + "source": "873", + "target": "6", + "sourceHandle": "51", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 767.3988903613488, + "sourceY": 574.5006486940537, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "vueflow__edge-24413-661", + "type": "default", + "source": "244", + "target": "6", + "sourceHandle": "13", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 749.4874144445799, + "sourceY": 680.1424930622838, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "308", + "type": "default", + "source": "979", + "target": "308", + "sourceHandle": "46", + "targetHandle": "17", + "data": {}, + "label": "", + "animated": true, + "sourceX": 463.42208597902874, + "sourceY": 386.83332778318527, + "targetX": 585.5419963013785, + "targetY": 345.6941732530918 + }, + { + "id": "vueflow__edge-30873-661", + "type": "default", + "source": "308", + "target": "6", + "sourceHandle": "73", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 795.0417833691082, + "sourceY": 345.6941732530918, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + } + ], + "position": [ + 447.2060780237498, + 377.80084997067763 + ], + "zoom": 0.8311688311688327, + "viewport": { + "x": 447.2060780237498, + "y": 377.80084997067763, + "zoom": 0.8311688311688327 + } + } +} \ No newline at end of file diff --git a/src/main/resources/script/sse.html b/metis-applicant/src/main/resources/script/sse.html similarity index 100% rename from src/main/resources/script/sse.html rename to metis-applicant/src/main/resources/script/sse.html diff --git a/metis-starter/pom.xml b/metis-starter/pom.xml new file mode 100644 index 0000000..ea66a81 --- /dev/null +++ b/metis-starter/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + com.metis + metis + 1.0.0-SNAPSHOT + + + metis-starter + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + dev.langchain4j + langchain4j-open-ai + + + dev.langchain4j + langchain4j-mcp + + + com.alibaba.fastjson2 + fastjson2 + 2.0.52 + + + org.projectlombok + lombok + + + com.mikesamuel + json-sanitizer + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + mysql + mysql-connector-java + + + cn.hutool + hutool-all + + + + org.mapstruct + mapstruct + + + org.projectlombok + lombok-mapstruct-binding + + + org.springdoc + springdoc-openapi-starter-webmvc-api + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + UTF-8 + + -parameters + + + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + + + \ No newline at end of file diff --git a/metis-starter/src/main/java/com/metis/config/MetisStarterAutoConfiguration.java b/metis-starter/src/main/java/com/metis/config/MetisStarterAutoConfiguration.java new file mode 100644 index 0000000..f96631b --- /dev/null +++ b/metis-starter/src/main/java/com/metis/config/MetisStarterAutoConfiguration.java @@ -0,0 +1,19 @@ +package com.metis.config; + +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +@MapperScan(basePackages = {"com.metis.mapper"}) +@ComponentScan("com.metis.*") +public class MetisStarterAutoConfiguration { + + + static { + + } + +} diff --git a/src/main/java/com/metis/flow/constant/BaseConstant.java b/metis-starter/src/main/java/com/metis/constant/BaseConstant.java similarity index 67% rename from src/main/java/com/metis/flow/constant/BaseConstant.java rename to metis-starter/src/main/java/com/metis/constant/BaseConstant.java index 7e0011a..bfac152 100644 --- a/src/main/java/com/metis/flow/constant/BaseConstant.java +++ b/metis-starter/src/main/java/com/metis/constant/BaseConstant.java @@ -1,4 +1,4 @@ -package com.metis.flow.constant; +package com.metis.constant; public interface BaseConstant { diff --git a/src/main/java/com/metis/controller/ProcessDefinitionController.java b/metis-starter/src/main/java/com/metis/controller/ProcessDefinitionController.java similarity index 83% rename from src/main/java/com/metis/controller/ProcessDefinitionController.java rename to metis-starter/src/main/java/com/metis/controller/ProcessDefinitionController.java index 58ebbf5..8f16bc5 100644 --- a/src/main/java/com/metis/controller/ProcessDefinitionController.java +++ b/metis-starter/src/main/java/com/metis/controller/ProcessDefinitionController.java @@ -2,7 +2,7 @@ package com.metis.controller; import com.metis.domain.bo.ProcessBo; import com.metis.facade.ProcessDefinitionFacade; -import com.metis.flow.domain.entity.App; +import com.metis.domain.entity.App; import com.metis.result.Result; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -16,9 +16,9 @@ public class ProcessDefinitionController { @PostMapping("/create") - public Result create(@RequestBody ProcessBo processBo) { - processDefinitionFacade.create(processBo); - return Result.ok(); + public Result create(@RequestBody ProcessBo processBo) { + Long workflowId = processDefinitionFacade.create(processBo); + return Result.ok(workflowId); } @PutMapping("/update") diff --git a/src/main/java/com/metis/flow/convert/BaseAppConvert.java b/metis-starter/src/main/java/com/metis/convert/BaseAppConvert.java similarity index 57% rename from src/main/java/com/metis/flow/convert/BaseAppConvert.java rename to metis-starter/src/main/java/com/metis/convert/BaseAppConvert.java index 5029018..7a1122b 100644 --- a/src/main/java/com/metis/flow/convert/BaseAppConvert.java +++ b/metis-starter/src/main/java/com/metis/convert/BaseAppConvert.java @@ -1,9 +1,10 @@ -package com.metis.flow.convert; +package com.metis.convert; -import com.metis.flow.domain.entity.*; -import com.metis.flow.domain.bo.BuildApp; -import com.metis.flow.domain.bo.CreateApp; -import com.metis.flow.domain.bo.UpdateApp; +import com.metis.domain.bo.BuildApp; +import com.metis.domain.bo.CreateApp; +import com.metis.domain.bo.UpdateApp; +import com.metis.domain.entity.App; +import com.metis.domain.entity.BaseApp; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @@ -23,6 +24,13 @@ public interface BaseAppConvert { * @param buildApp 基础应用 * @return {@link App } */ + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(target = "workflowId", ignore = true), + @Mapping(target = "createTime", ignore = true), + @Mapping(target = "version", ignore = true), + @Mapping(target = "defaultUse", ignore = true), + }) App toApp(BuildApp buildApp); /** @@ -32,7 +40,7 @@ public interface BaseAppConvert { * @return {@link App } */ @Mappings({ - @Mapping(target = "graph", expression = "java(com.alibaba.fastjson2.JSON.parseObject(baseApp.getGraphJson(), com.metis.flow.domain.bo.Graph.class))"), + @Mapping(target = "graph", expression = "java(com.alibaba.fastjson2.JSON.parseObject(baseApp.getGraphJson(), com.metis.domain.entity.base.Graph.class))"), @Mapping(target = "workflowId", source = "id") }) App toApp(BaseApp baseApp); @@ -44,7 +52,13 @@ public interface BaseAppConvert { * @return {@link BaseApp } */ @Mappings({ - @Mapping(target = "graphJson", expression = "java(com.alibaba.fastjson2.JSON.toJSONString(buildApp.getGraph()))") + @Mapping(target = "graphJson", expression = "java(com.alibaba.fastjson2.JSON.toJSONString(buildApp.getGraph()))"), + @Mapping(target = "createTime", ignore = true), + @Mapping(target = "updateTime", ignore = true), + @Mapping(target = "isDeleted", ignore = true), + @Mapping(target = "id", ignore = true), + @Mapping(target = "createUserId", ignore = true), + @Mapping(target = "defaultUse", ignore = true) }) BaseApp toBaseApp(BuildApp buildApp); @@ -63,6 +77,9 @@ public interface BaseAppConvert { * @param createApp 创建应用程序 * @return {@link BuildApp } */ + @Mappings({ + @Mapping(target = "appId", ignore = true) + }) BuildApp toBuildApp(CreateApp createApp); /** @@ -71,6 +88,9 @@ public interface BaseAppConvert { * @param updateApp 更新应用程序 * @return {@link BuildApp } */ + @Mappings({ + @Mapping(target = "userId", ignore = true) + }) BuildApp toBuildApp(UpdateApp updateApp); } diff --git a/metis-starter/src/main/java/com/metis/convert/GraphConvert.java b/metis-starter/src/main/java/com/metis/convert/GraphConvert.java new file mode 100644 index 0000000..da2fa78 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/convert/GraphConvert.java @@ -0,0 +1,17 @@ +package com.metis.convert; + + +import com.metis.domain.bo.GraphBO; +import com.metis.domain.entity.base.Graph; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface GraphConvert { + + GraphConvert INSTANCE = Mappers.getMapper(GraphConvert.class); + + + Graph toEntity(GraphBO graph); + +} diff --git a/src/main/java/com/metis/domain/SimpleBaseEntity.java b/metis-starter/src/main/java/com/metis/domain/SimpleBaseEntity.java similarity index 100% rename from src/main/java/com/metis/domain/SimpleBaseEntity.java rename to metis-starter/src/main/java/com/metis/domain/SimpleBaseEntity.java diff --git a/src/main/java/com/metis/flow/domain/bo/BuildApp.java b/metis-starter/src/main/java/com/metis/domain/bo/BuildApp.java similarity index 88% rename from src/main/java/com/metis/flow/domain/bo/BuildApp.java rename to metis-starter/src/main/java/com/metis/domain/bo/BuildApp.java index fd78ff5..28d6540 100644 --- a/src/main/java/com/metis/flow/domain/bo/BuildApp.java +++ b/metis-starter/src/main/java/com/metis/domain/bo/BuildApp.java @@ -1,5 +1,6 @@ -package com.metis.flow.domain.bo; +package com.metis.domain.bo; +import com.metis.domain.entity.base.Graph; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/metis/flow/domain/bo/CreateApp.java b/metis-starter/src/main/java/com/metis/domain/bo/CreateApp.java similarity index 88% rename from src/main/java/com/metis/flow/domain/bo/CreateApp.java rename to metis-starter/src/main/java/com/metis/domain/bo/CreateApp.java index 42f2a66..a8a9c1a 100644 --- a/src/main/java/com/metis/flow/domain/bo/CreateApp.java +++ b/metis-starter/src/main/java/com/metis/domain/bo/CreateApp.java @@ -1,5 +1,6 @@ -package com.metis.flow.domain.bo; +package com.metis.domain.bo; +import com.metis.domain.entity.base.Graph; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/metis-starter/src/main/java/com/metis/domain/bo/EdgeBO.java b/metis-starter/src/main/java/com/metis/domain/bo/EdgeBO.java new file mode 100644 index 0000000..0de8c4b --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/bo/EdgeBO.java @@ -0,0 +1,66 @@ +package com.metis.domain.bo; + +import com.metis.enums.EdgeType; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class EdgeBO { + + /** + * 唯一标识符 + */ + @NotNull(message = "唯一标识符不能为空") + private String id; + + /** + * 标签 + */ + private String label; + + /** + * 节点类型 + */ + @NotNull(message = "线类型不能为空") + private EdgeType type; + + /** + * 源节点ID,对应节点id + */ + @NotNull(message = "源节点ID不能为空") + private Long source; + + /** + * 目标节点ID,对应节点id + */ + @NotNull(message = "目标节点ID不能为空") + private Long target; + /** + * 源句柄id + */ + @NotNull(message = "源句柄ID不能为空") + private Long sourceHandle; + + /** + * 目标句柄id + */ + @NotNull(message = "目标句柄ID不能为空") + private Long targetHandle; + + /** + * 边是否动画true/false + */ + private Boolean animated; + + /** + * 开始标志 + */ + private String markerStart; + + /** + * 结束标记 + */ + private String markerEnd; + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/bo/GraphBO.java b/metis-starter/src/main/java/com/metis/domain/bo/GraphBO.java new file mode 100644 index 0000000..a5b51f5 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/bo/GraphBO.java @@ -0,0 +1,43 @@ +package com.metis.domain.bo; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class GraphBO { + + /** + * 边缘 + */ + @Valid + @NotEmpty(message = "连线不能为空") + private List edges; + + /** + * 节点 + */ + @Valid + @NotEmpty(message = "节点不能为空") + private List nodes; + + + /** + * 位置 + */ + private List position; + + /** + * 变焦 + */ + private Double zoom; + + /** + * 视窗 + */ + private ViewportBo viewport; + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/bo/HandleBO.java b/metis-starter/src/main/java/com/metis/domain/bo/HandleBO.java new file mode 100644 index 0000000..510557d --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/bo/HandleBO.java @@ -0,0 +1,36 @@ +package com.metis.domain.bo; + +import com.metis.enums.HandleType; +import com.metis.enums.PositionType; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 句柄对象 + */ +@Data +public class HandleBO { + /** + * 句柄id + */ + @NotNull(message = "句柄id不能为空") + private Long id; + + /** + * 句柄类型 + */ + @NotNull(message = "句柄类型不能为空") + private HandleType type; + + /** + * 句柄位置 + */ + @NotNull(message = "句柄位置不能为空") + private PositionType position; + + /** + * 是否可以连接 + */ + @NotNull(message = "是否可以连接不能为空") + private Boolean connectable; +} diff --git a/metis-starter/src/main/java/com/metis/domain/bo/NodeBO.java b/metis-starter/src/main/java/com/metis/domain/bo/NodeBO.java new file mode 100644 index 0000000..9bab140 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/bo/NodeBO.java @@ -0,0 +1,60 @@ +package com.metis.domain.bo; + +import com.metis.enums.NodeType; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class NodeBO { + + /** + * id + */ + @NotNull(message = "节点id不能为空") + private Long id; + + /** + * 类型 + */ + @NotNull(message = "节点类型不能为空") + private NodeType type; + + /** + * 自定义类型 + */ + private String customType; + + /** + * 位置 + */ + @Valid + @NotNull(message = "节点位置不能为空") + private PositionBO position; + + /** + * 业务数据 + */ + @Valid + @NotNull(message = "节点业务数据不能为空") + private NodeDataBO data; + + /** + * 宽度 + */ +// @NotNull(message = "节点宽度不能为空") + private Integer width; + + /** + * 高度 + */ +// @NotNull(message = "节点高度不能为空") + private Integer height; + + /** + * 节点是否选中 + */ + private Boolean selected; + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/bo/NodeDataBO.java b/metis-starter/src/main/java/com/metis/domain/bo/NodeDataBO.java new file mode 100644 index 0000000..04e0c82 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/bo/NodeDataBO.java @@ -0,0 +1,45 @@ +package com.metis.domain.bo; + +import com.alibaba.fastjson2.JSONObject; +import com.metis.enums.PositionType; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class NodeDataBO { + + /** + * 标签 + */ + @NotBlank(message = "标签不能为空") + private String label; + + /** + * 图标 + */ + private String icon; + + /** + * 工具栏位置 + */ + private PositionType toolbarPosition; + + + /** + * 配置 + */ + private JSONObject config; + + /** + * 句柄列表 + */ + @Valid + @NotEmpty(message = "句柄列表不能为空") + private List handles; + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/bo/PositionBO.java b/metis-starter/src/main/java/com/metis/domain/bo/PositionBO.java new file mode 100644 index 0000000..00f0b50 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/bo/PositionBO.java @@ -0,0 +1,20 @@ +package com.metis.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PositionBO { + /** + * x坐标 + */ + @NotNull(message = "x坐标不能为空") + private Double x; + + /** + * y坐标 + */ + @NotNull(message = "y坐标不能为空") + private Double y; + +} diff --git a/src/main/java/com/metis/domain/bo/ProcessBo.java b/metis-starter/src/main/java/com/metis/domain/bo/ProcessBo.java similarity index 79% rename from src/main/java/com/metis/domain/bo/ProcessBo.java rename to metis-starter/src/main/java/com/metis/domain/bo/ProcessBo.java index 325018e..c0075af 100644 --- a/src/main/java/com/metis/domain/bo/ProcessBo.java +++ b/metis-starter/src/main/java/com/metis/domain/bo/ProcessBo.java @@ -1,7 +1,7 @@ package com.metis.domain.bo; import com.metis.enums.YesOrNoEnum; -import com.metis.flow.domain.bo.Graph; +import com.metis.domain.bo.GraphBO; import lombok.Data; @Data @@ -13,7 +13,7 @@ public class ProcessBo { private String description; - private Graph graph; + private GraphBO graph; private YesOrNoEnum defaultUse; diff --git a/src/main/java/com/metis/flow/domain/bo/UpdateApp.java b/metis-starter/src/main/java/com/metis/domain/bo/UpdateApp.java similarity index 89% rename from src/main/java/com/metis/flow/domain/bo/UpdateApp.java rename to metis-starter/src/main/java/com/metis/domain/bo/UpdateApp.java index a6a0050..e4c3238 100644 --- a/src/main/java/com/metis/flow/domain/bo/UpdateApp.java +++ b/metis-starter/src/main/java/com/metis/domain/bo/UpdateApp.java @@ -1,6 +1,7 @@ -package com.metis.flow.domain.bo; +package com.metis.domain.bo; import com.metis.enums.YesOrNoEnum; +import com.metis.domain.entity.base.Graph; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/metis-starter/src/main/java/com/metis/domain/bo/ViewportBo.java b/metis-starter/src/main/java/com/metis/domain/bo/ViewportBo.java new file mode 100644 index 0000000..7209088 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/bo/ViewportBo.java @@ -0,0 +1,10 @@ +package com.metis.domain.bo; + +import lombok.Data; + +@Data +public class ViewportBo { + private Double x; + private Double y; + private Double zoom; +} diff --git a/metis-starter/src/main/java/com/metis/domain/context/RunningContext.java b/metis-starter/src/main/java/com/metis/domain/context/RunningContext.java new file mode 100644 index 0000000..c2d1d68 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/context/RunningContext.java @@ -0,0 +1,68 @@ +package com.metis.domain.context; + +import com.alibaba.fastjson2.JSONObject; +import com.metis.runner.FlowRunningContext; +import lombok.Builder; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@Getter +@Builder +public class RunningContext { + + /** + * 系统数据 + */ + private SysContext sys; + + /** + * 自定义数据 + */ + private JSONObject custom; + + /** + * 节点运行上下文, 需要数据进行传递 + */ + private Map nodeRunningContext; + + /** + * 下一个运行节点id集合, 可能是多个, 执行器每一次清空该节点 + */ + private Set nextRunNodeId; + + + /** + * 添加节点运行环境 + * + * @param nodeId 节点id + * @param nodeRunningContext 节点运行背景信息 + */ + public void addNodeRunningContext(Long nodeId, JSONObject nodeRunningContext) { + this.nodeRunningContext.put(nodeId, nodeRunningContext); + } + + public JSONObject getRunningContext(Long nodeId) { + return this.nodeRunningContext.get(nodeId); + } + + + /** + * 构建上下文 + * + * @param context 上下文 + * @return {@link RunningContext } + */ + public static RunningContext buildContext(SysContext sysContext, FlowRunningContext context) { + return RunningContext.builder() + .sys(sysContext) + .custom(context.getCustom()) + .nodeRunningContext(new HashMap<>()) + .build(); + } + + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/context/RunningResult.java b/metis-starter/src/main/java/com/metis/domain/context/RunningResult.java new file mode 100644 index 0000000..556e6a6 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/context/RunningResult.java @@ -0,0 +1,68 @@ +package com.metis.domain.context; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Builder; +import lombok.Data; + +import java.util.Set; + +@Data +@Builder +public class RunningResult { + + /** + * 节点上下文 + */ + private JSONObject nodeContext; + + /** + * 下一个运行节点id, 一些特殊节点需要, 必须条件节点满足后, 才会运行下一个节点 + */ + private Set nextRunNodeId; + + + /** + * 构建结果 + * + * @param nodeContext 节点上下文 + * @param nextRunNodeId 下一个运行节点id + * @return {@link RunningResult } + */ + public static RunningResult buildResult(JSONObject nodeContext, Set nextRunNodeId) { + return RunningResult.builder() + .nodeContext(nodeContext) + .nextRunNodeId(nextRunNodeId) + .build(); + } + + /** + * 构建结果 + * + * @param nextRunNodeId 下一个运行节点id + * @return {@link RunningResult } + */ + public static RunningResult buildResult(Set nextRunNodeId) { + return RunningResult.builder() + .nextRunNodeId(nextRunNodeId) + .build(); + } + + /** + * 构建结果 + * + * @param nodeContext 节点上下文 + * @return {@link RunningResult } + */ + public static RunningResult buildResult(JSONObject nodeContext) { + return RunningResult.builder() + .nodeContext(nodeContext) + .build(); + } + + public static RunningResult buildResult() { + return RunningResult.builder() + .build(); + } + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/context/SysContext.java b/metis-starter/src/main/java/com/metis/domain/context/SysContext.java new file mode 100644 index 0000000..a2327e9 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/context/SysContext.java @@ -0,0 +1,51 @@ +package com.metis.domain.context; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class SysContext { + + /** + * 文件列表 + */ + private List files; + + /** + * 沟通的内容 + */ + private String query; + + /** + * 对话数 + */ + private Integer dialogueCount; + + /** + * 沟通的id + */ + private String conversationId; + + /** + * 应用程序id + */ + private Long appId; + + /** + * 用户id + */ + private Long userId; + + /** + * 工作流id + */ + private Long workflowId; + + /** + * 实例id + */ + private Long instanceId; +} diff --git a/src/main/java/com/metis/flow/domain/entity/App.java b/metis-starter/src/main/java/com/metis/domain/entity/App.java similarity index 88% rename from src/main/java/com/metis/flow/domain/entity/App.java rename to metis-starter/src/main/java/com/metis/domain/entity/App.java index 5b97af1..ef48d44 100644 --- a/src/main/java/com/metis/flow/domain/entity/App.java +++ b/metis-starter/src/main/java/com/metis/domain/entity/App.java @@ -1,8 +1,8 @@ -package com.metis.flow.domain.entity; +package com.metis.domain.entity; import com.metis.enums.YesOrNoEnum; -import com.metis.flow.domain.bo.Graph; +import com.metis.domain.entity.base.Graph; import lombok.Data; import java.time.LocalDateTime; diff --git a/src/main/java/com/metis/flow/domain/entity/BaseApp.java b/metis-starter/src/main/java/com/metis/domain/entity/BaseApp.java similarity index 97% rename from src/main/java/com/metis/flow/domain/entity/BaseApp.java rename to metis-starter/src/main/java/com/metis/domain/entity/BaseApp.java index 48e9521..53204e0 100644 --- a/src/main/java/com/metis/flow/domain/entity/BaseApp.java +++ b/metis-starter/src/main/java/com/metis/domain/entity/BaseApp.java @@ -1,4 +1,4 @@ -package com.metis.flow.domain.entity; +package com.metis.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/metis-starter/src/main/java/com/metis/domain/entity/GraphDto.java b/metis-starter/src/main/java/com/metis/domain/entity/GraphDto.java new file mode 100644 index 0000000..5de1cc2 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/GraphDto.java @@ -0,0 +1,131 @@ +package com.metis.domain.entity; + + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Graph; +import com.metis.domain.entity.base.Node; +import com.metis.enums.NodeType; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class GraphDto { + + private final Map nodeMap; + + private final Map nodeReadyMap; + + private final Map> edgeMap; + + private final Map> adjacencyList = new HashMap<>(); + + private final List sortedNodes = new ArrayList<>(); + + + public List getSortedNodes() { + return new ArrayList<>(sortedNodes); + } + + public List getEdgeNodeId(Long nodeId) { + return edgeMap.getOrDefault(nodeId, new ArrayList<>()); + } + + public Node getEndNode(){ + return sortedNodes.stream() + .filter(node -> NodeType.END.equals(node.getType())) + .findFirst() + .orElse(null); + } + + public Node getNode(Long nodeId) { + return nodeMap.get(nodeId); + } + + public void updateNodeReadyMap(Long nodeId, Boolean ready) { + nodeReadyMap.put(nodeId, ready); + } + + public Boolean isNodeReady(Long nodeId) { + return nodeReadyMap.get(nodeId); + } + + + private GraphDto(List nodes, List edges) { + this.edgeMap = edges.stream() + .collect(Collectors.groupingBy(Edge::getSource)); + this.nodeMap = nodes.stream() + .collect(Collectors.toMap(Node::getId, Function.identity())); + this.nodeReadyMap = nodes.stream() + .collect(Collectors.toMap(Node::getId, node -> false)); + initAdjacencyList(edges); + List nodeList = topologicalSort(); + this.sortedNodes.addAll(nodeList); + Node node = sortedNodes.get(0); + if (NodeType.START.equals(node.getType())) { + nodeReadyMap.put(node.getId(), true); + } + } + + private void initAdjacencyList(List edges) { + for (Edge edge : edges) { + List targetList = adjacencyList.getOrDefault(edge.getSource(), new ArrayList<>()); + targetList.add(edge.getTarget()); + adjacencyList.put(edge.getSource(), targetList); + } + } + + + /** + * 拓扑排序 + * + * @return {@link List }<{@link Node }> + */ + private List topologicalSort() { + List sortedNodes = new ArrayList<>(); + Set visited = new HashSet<>(); + Set visiting = new HashSet<>(); + for (Long nodeId : nodeMap.keySet()) { + if (!visited.contains(nodeId)) { + dfs(nodeId, visited, visiting, sortedNodes); + } + } + Collections.reverse(sortedNodes); + return sortedNodes; + } + + /** + * 深度遍历找到运行顺序 + * + * @param nodeId 节点id + * @param visited 参观了 + * @param visiting 参观 + * @param sortedNodes 排序节点 + */ + private void dfs(Long nodeId, Set visited, Set visiting, List sortedNodes) { + if (visiting.contains(nodeId)) { + throw new IllegalStateException("Cycle detected in the graph"); + } + if (!visited.contains(nodeId)) { + visiting.add(nodeId); + for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) { + dfs(neighbor, visited, visiting, sortedNodes); + } + visiting.remove(nodeId); + visited.add(nodeId); + sortedNodes.add(nodeMap.get(nodeId)); + } + } + + + /** + * 构建对象 + * + * @param graph 图 + * @return {@link GraphDto } + */ + public static GraphDto of(Graph graph) { + return new GraphDto(graph.getNodes(), graph.getEdges()); + } + +} diff --git a/src/main/java/com/metis/flow/domain/entity/base/Edge.java b/metis-starter/src/main/java/com/metis/domain/entity/base/Edge.java similarity index 58% rename from src/main/java/com/metis/flow/domain/entity/base/Edge.java rename to metis-starter/src/main/java/com/metis/domain/entity/base/Edge.java index ce657d1..d389fec 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/Edge.java +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/Edge.java @@ -1,7 +1,6 @@ -package com.metis.flow.domain.entity.base; +package com.metis.domain.entity.base; -import com.metis.flow.enums.EdgeType; -import jakarta.validation.constraints.NotBlank; +import com.metis.enums.EdgeType; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -11,7 +10,7 @@ public class Edge { /** * 唯一标识符 */ - @NotBlank(message = "唯一标识符不能为空") + @NotNull(message = "唯一标识符不能为空") private String id; /** @@ -28,25 +27,25 @@ public class Edge { /** * 源节点ID,对应节点id */ - @NotBlank(message = "源节点ID不能为空") - private String source; + @NotNull(message = "源节点ID不能为空") + private Long source; /** * 目标节点ID,对应节点id */ - @NotBlank(message = "目标节点ID不能为空") - private String target; + @NotNull(message = "目标节点ID不能为空") + private Long target; /** * 源句柄id */ - @NotBlank(message = "源句柄ID不能为空") - private String sourceHandle; + @NotNull(message = "源句柄ID不能为空") + private Long sourceHandle; /** * 目标句柄id */ - @NotBlank(message = "目标句柄ID不能为空") - private String targetHandle; + @NotNull(message = "目标句柄ID不能为空") + private Long targetHandle; /** * 边是否动画true/false diff --git a/metis-starter/src/main/java/com/metis/domain/entity/base/Graph.java b/metis-starter/src/main/java/com/metis/domain/entity/base/Graph.java new file mode 100644 index 0000000..c440eb3 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/Graph.java @@ -0,0 +1,47 @@ +package com.metis.domain.entity.base; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Graph { + + /** + * 边缘 + */ + @Valid + @NotEmpty(message = "连线不能为空") + private List edges; + + /** + * 节点 + */ + @Valid + @NotEmpty(message = "节点不能为空") + private List nodes; + + /** + * 位置 + */ + private List position; + + /** + * 变焦 + */ + private Double zoom; + + /** + * 视窗 + */ + private Viewport viewport; + + + +} diff --git a/src/main/java/com/metis/flow/domain/entity/base/Handle.java b/metis-starter/src/main/java/com/metis/domain/entity/base/Handle.java similarity index 68% rename from src/main/java/com/metis/flow/domain/entity/base/Handle.java rename to metis-starter/src/main/java/com/metis/domain/entity/base/Handle.java index f49dda3..f0e3e32 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/Handle.java +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/Handle.java @@ -1,8 +1,7 @@ -package com.metis.flow.domain.entity.base; +package com.metis.domain.entity.base; -import com.metis.flow.enums.PositionType; -import com.metis.flow.enums.HandleType; -import jakarta.validation.constraints.NotBlank; +import com.metis.enums.HandleType; +import com.metis.enums.PositionType; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -14,8 +13,8 @@ public class Handle { /** * 句柄id */ - @NotBlank(message = "句柄id不能为空") - private String id; + @NotNull(message = "句柄id不能为空") + private Long id; /** * 句柄类型 diff --git a/metis-starter/src/main/java/com/metis/domain/entity/base/Node.java b/metis-starter/src/main/java/com/metis/domain/entity/base/Node.java new file mode 100644 index 0000000..c5d976b --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/Node.java @@ -0,0 +1,84 @@ +package com.metis.domain.entity.base; + +import cn.hutool.core.util.ObjectUtil; +import com.metis.enums.NodeType; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class Node { + + /** + * id + */ + @NotNull(message = "节点id不能为空") + private Long id; + + /** + * 类型 + */ + @NotNull(message = "节点类型不能为空") + private NodeType type; + + private String customType; + + /** + * 位置 + */ + @Valid + @NotNull(message = "节点位置不能为空") + private Position position; + + /** + * 业务数据 + */ + @Valid + @NotNull(message = "节点业务数据不能为空") + private NodeData data; + + /** + * 宽度 + */ +// @NotNull(message = "节点宽度不能为空") + private Integer width; + + /** + * 高度 + */ +// @NotNull(message = "节点高度不能为空") + private Integer height; + + /** + * 节点是否选中 + */ + private Boolean selected; + + private transient Class configClass; + + + /** + * 获取配置 + * + * @return {@link T } + */ + public T getConfig() { + if (ObjectUtil.isNull(data.getConfig())) { + return null; + } + return (T) data.getConfig().to(configClass); + } + + /** + * 设置配置类 + * + * @param configClass 配置类 + */ + public void setConfigClass(Class configClass) { + if (ObjectUtil.isNotNull(this.configClass)) { + return; + } + this.configClass = configClass; + } + +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/base/NodeConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/base/NodeConfig.java new file mode 100644 index 0000000..f96afd2 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/NodeConfig.java @@ -0,0 +1,4 @@ +package com.metis.domain.entity.base; + +public abstract class NodeConfig { +} diff --git a/src/main/java/com/metis/flow/domain/entity/base/NodeData.java b/metis-starter/src/main/java/com/metis/domain/entity/base/NodeData.java similarity index 63% rename from src/main/java/com/metis/flow/domain/entity/base/NodeData.java rename to metis-starter/src/main/java/com/metis/domain/entity/base/NodeData.java index 5ae8089..9aa53f7 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/NodeData.java +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/NodeData.java @@ -1,10 +1,10 @@ -package com.metis.flow.domain.entity.base; +package com.metis.domain.entity.base; import com.alibaba.fastjson2.JSONObject; -import com.metis.flow.enums.PositionType; +import com.metis.enums.PositionType; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import lombok.Data; import java.util.List; @@ -15,6 +15,7 @@ public class NodeData { /** * 标签 */ + @NotBlank(message = "标签不能为空") private String label; /** @@ -27,6 +28,7 @@ public class NodeData { */ private PositionType toolbarPosition; + /** * 配置 */ @@ -36,8 +38,7 @@ public class NodeData { * 句柄列表 */ @Valid - @NotNull(message = "句柄列表不能为空") - @Size(min = 1, message = "句柄列表不能为空") + @NotEmpty(message = "句柄列表不能为空") private List handles; diff --git a/metis-starter/src/main/java/com/metis/domain/entity/base/NodeVariable.java b/metis-starter/src/main/java/com/metis/domain/entity/base/NodeVariable.java new file mode 100644 index 0000000..3058bbd --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/NodeVariable.java @@ -0,0 +1,98 @@ +package com.metis.domain.entity.base; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.metis.enums.FileUploadType; +import com.metis.enums.NodeVariableType; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +public class NodeVariable { + + /** + * 参数字段 + */ + @NotBlank(message = "参数字段不能为空") + private String variable; + + /** + * 标签 + */ + private String label; + + /** + * 最大长度 + */ + private Integer maxLength; + + /** + * 类型 + */ + @NotNull(message = "类型不能为空") + private String type; + + /** + * 是否必填 + */ + @NotNull(message = "是否必填不能为空") + private Boolean required; + + /** + * 选项 + */ + @Valid + private List options; + + /** + * 允许上传方式 + */ + private List allowedFileUploadMethods; + + /** + * 允许文件类型 + */ + private List allowedFileTypes; + + /** + * 允许文件扩展名 + */ + private List allowedFileExtensions; + + + @JsonIgnore + public Object getValue(JSONObject custom) { + Object serializable = getSerializable(custom); + Assert.isTrue(!(ObjectUtil.isNull(serializable) && ObjectUtil.isNotNull(required) && required), "参数字段 {} 的值不能为空", variable); + return serializable; + } + + private Object getSerializable(JSONObject custom) { + switch (getVariableType()) { + case TEXT_INPUT, PARAGRAPH, SELECT, FILE -> { + return custom.getString(variable); + } + case NUMBER -> { + return custom.getInteger(variable); + } + case FILE_LIST -> { + return custom.getList(variable, String.class); + } + default -> throw new RuntimeException("不支持的类型"); + } + } + + + @JsonIgnore + public NodeVariableType getVariableType() { + return NodeVariableType.get(type); + } + + +} diff --git a/src/main/java/com/metis/flow/domain/entity/base/Position.java b/metis-starter/src/main/java/com/metis/domain/entity/base/Position.java similarity index 87% rename from src/main/java/com/metis/flow/domain/entity/base/Position.java rename to metis-starter/src/main/java/com/metis/domain/entity/base/Position.java index 291cf55..2beec9f 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/Position.java +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/Position.java @@ -1,4 +1,4 @@ -package com.metis.flow.domain.entity.base; +package com.metis.domain.entity.base; import jakarta.validation.constraints.NotNull; import lombok.Data; diff --git a/metis-starter/src/main/java/com/metis/domain/entity/base/VariableOption.java b/metis-starter/src/main/java/com/metis/domain/entity/base/VariableOption.java new file mode 100644 index 0000000..7ac3e13 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/VariableOption.java @@ -0,0 +1,22 @@ +package com.metis.domain.entity.base; + + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class VariableOption { + + /** + * 标签 + */ + private String label; + + /** + * 值 + */ + @NotNull(message = "值不能为空") + private String value; + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/base/Viewport.java b/metis-starter/src/main/java/com/metis/domain/entity/base/Viewport.java new file mode 100644 index 0000000..039f996 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/base/Viewport.java @@ -0,0 +1,10 @@ +package com.metis.domain.entity.base; + +import lombok.Data; + +@Data +public class Viewport { + private Double x; + private Double y; + private Double zoom; +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/node/DocumentExtractorNodeConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/config/node/DocumentExtractorNodeConfig.java new file mode 100644 index 0000000..eae223c --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/node/DocumentExtractorNodeConfig.java @@ -0,0 +1,15 @@ +package com.metis.domain.entity.config.node; + +import com.metis.domain.entity.base.NodeConfig; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class DocumentExtractorNodeConfig extends NodeConfig { + + @NotBlank(message = "文件类型不能为空") + private String fileType; + +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/node/EndNodeConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/config/node/EndNodeConfig.java new file mode 100644 index 0000000..202e601 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/node/EndNodeConfig.java @@ -0,0 +1,10 @@ +package com.metis.domain.entity.config.node; + +import com.metis.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EndNodeConfig extends NodeConfig { +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/node/LLMNodeConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/config/node/LLMNodeConfig.java new file mode 100644 index 0000000..6a2df06 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/node/LLMNodeConfig.java @@ -0,0 +1,10 @@ +package com.metis.domain.entity.config.node; + +import com.metis.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class LLMNodeConfig extends NodeConfig { +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/node/QuestionClassifierConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/config/node/QuestionClassifierConfig.java new file mode 100644 index 0000000..caef097 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/node/QuestionClassifierConfig.java @@ -0,0 +1,10 @@ +package com.metis.domain.entity.config.node; + +import com.metis.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class QuestionClassifierConfig extends NodeConfig { +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/node/StartNodeConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/config/node/StartNodeConfig.java new file mode 100644 index 0000000..4f37e13 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/node/StartNodeConfig.java @@ -0,0 +1,19 @@ +package com.metis.domain.entity.config.node; + +import com.metis.domain.entity.base.NodeConfig; +import com.metis.domain.entity.base.NodeVariable; +import jakarta.validation.Valid; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +public class StartNodeConfig extends NodeConfig { + + @Valid + private List variables; + + +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/package-info.java b/metis-starter/src/main/java/com/metis/domain/entity/config/package-info.java new file mode 100644 index 0000000..e836bd2 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/package-info.java @@ -0,0 +1 @@ +package com.metis.domain.entity.config; \ No newline at end of file diff --git a/src/main/java/com/metis/flow/domain/query/AppQuery.java b/metis-starter/src/main/java/com/metis/domain/query/AppQuery.java similarity index 82% rename from src/main/java/com/metis/flow/domain/query/AppQuery.java rename to metis-starter/src/main/java/com/metis/domain/query/AppQuery.java index 2cd0ebe..5fb28ce 100644 --- a/src/main/java/com/metis/flow/domain/query/AppQuery.java +++ b/metis-starter/src/main/java/com/metis/domain/query/AppQuery.java @@ -1,4 +1,4 @@ -package com.metis.flow.domain.query; +package com.metis.domain.query; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/metis/flow/engine/AppEngineService.java b/metis-starter/src/main/java/com/metis/engine/AppEngineService.java similarity index 73% rename from src/main/java/com/metis/flow/engine/AppEngineService.java rename to metis-starter/src/main/java/com/metis/engine/AppEngineService.java index d2fe8d9..dac3ee6 100644 --- a/src/main/java/com/metis/flow/engine/AppEngineService.java +++ b/metis-starter/src/main/java/com/metis/engine/AppEngineService.java @@ -1,9 +1,10 @@ -package com.metis.flow.engine; +package com.metis.engine; -import com.metis.flow.domain.query.AppQuery; -import com.metis.flow.domain.entity.App; -import com.metis.flow.domain.bo.CreateApp; -import com.metis.flow.domain.bo.UpdateApp; + +import com.metis.domain.bo.CreateApp; +import com.metis.domain.bo.UpdateApp; +import com.metis.domain.entity.App; +import com.metis.domain.query.AppQuery; import java.util.List; @@ -26,6 +27,14 @@ public interface AppEngineService { List list(AppQuery query); + /** + * 通过应用id获取 + * + * @param appId 应用程序id + * @return {@link App } + */ + App getByAppId(Long appId); + /** * 按身份证领取 * @@ -37,10 +46,10 @@ public interface AppEngineService { /** * 通过部署id获取 * - * @param deploymentId 部署id + * @param workflowId 部署id * @return {@link App } */ - App getByDeploymentId(Long deploymentId); + App getByWorkflowId(Long workflowId); /** * 按id列表 diff --git a/metis-starter/src/main/java/com/metis/engine/AppFlowEngineRunnerService.java b/metis-starter/src/main/java/com/metis/engine/AppFlowEngineRunnerService.java new file mode 100644 index 0000000..fe0d367 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/engine/AppFlowEngineRunnerService.java @@ -0,0 +1,27 @@ +package com.metis.engine; + +import com.metis.runner.FlowRunningContext; +import com.metis.runner.RunnerResult; + +/** + * 应用引擎运行器服务 + * + * @author clay + * @date 2025/04/07 + */ +public interface AppFlowEngineRunnerService { + + + /** + * 运行 + * + * @param context 上下文 + * @return {@link RunnerResult } + */ + RunnerResult running(FlowRunningContext context); + + + + + +} diff --git a/src/main/java/com/metis/flow/engine/impl/AppEngineServiceImpl.java b/metis-starter/src/main/java/com/metis/engine/impl/AppEngineServiceImpl.java similarity index 84% rename from src/main/java/com/metis/flow/engine/impl/AppEngineServiceImpl.java rename to metis-starter/src/main/java/com/metis/engine/impl/AppEngineServiceImpl.java index 7a5b117..0a3a993 100644 --- a/src/main/java/com/metis/flow/engine/impl/AppEngineServiceImpl.java +++ b/metis-starter/src/main/java/com/metis/engine/impl/AppEngineServiceImpl.java @@ -1,19 +1,20 @@ -package com.metis.flow.engine.impl; +package com.metis.engine.impl; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; +import com.metis.constant.BaseConstant; +import com.metis.convert.BaseAppConvert; +import com.metis.domain.bo.BuildApp; +import com.metis.domain.bo.CreateApp; +import com.metis.domain.bo.UpdateApp; +import com.metis.domain.entity.App; +import com.metis.domain.entity.BaseApp; +import com.metis.domain.query.AppQuery; +import com.metis.engine.AppEngineService; import com.metis.enums.YesOrNoEnum; -import com.metis.flow.constant.BaseConstant; -import com.metis.flow.convert.BaseAppConvert; -import com.metis.flow.domain.bo.BuildApp; -import com.metis.flow.domain.bo.CreateApp; -import com.metis.flow.domain.bo.UpdateApp; -import com.metis.flow.domain.query.AppQuery; -import com.metis.flow.domain.entity.*; -import com.metis.flow.engine.AppEngineService; -import com.metis.flow.service.BaseAppService; -import com.metis.flow.validator.ValidatorService; +import com.metis.service.BaseAppService; +import com.metis.validator.ValidatorService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -42,6 +43,12 @@ public class AppEngineServiceImpl implements AppEngineService { return BaseAppConvert.INSTANCE.toApps(list); } + @Override + public App getByAppId(Long appId) { + BaseApp baseApp = baseAppService.getByAppId(appId); + return BaseAppConvert.INSTANCE.toApp(baseApp); + } + @Override public App getByAppId(Long appId, Integer version) { BaseApp baseApp = baseAppService.getByAppIdAndVersion(appId, version); @@ -49,8 +56,8 @@ public class AppEngineServiceImpl implements AppEngineService { } @Override - public App getByDeploymentId(Long deploymentId) { - BaseApp baseApp = baseAppService.getById(deploymentId); + public App getByWorkflowId(Long workflowId) { + BaseApp baseApp = baseAppService.getById(workflowId); return BaseAppConvert.INSTANCE.toApp(baseApp); } @@ -61,6 +68,7 @@ public class AppEngineServiceImpl implements AppEngineService { } @Override + @Transactional(rollbackFor = Exception.class) public App create(CreateApp createApp) { BuildApp buildApp = BaseAppConvert.INSTANCE.toBuildApp(createApp); // 校验 diff --git a/metis-starter/src/main/java/com/metis/engine/impl/AppFlowEngineRunnerServiceImpl.java b/metis-starter/src/main/java/com/metis/engine/impl/AppFlowEngineRunnerServiceImpl.java new file mode 100644 index 0000000..9f64f8f --- /dev/null +++ b/metis-starter/src/main/java/com/metis/engine/impl/AppFlowEngineRunnerServiceImpl.java @@ -0,0 +1,159 @@ +package com.metis.engine.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.context.SysContext; +import com.metis.domain.entity.App; +import com.metis.domain.entity.GraphDto; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.engine.AppEngineService; +import com.metis.engine.AppFlowEngineRunnerService; +import com.metis.enums.NodeType; +import com.metis.runner.FlowRunningContext; +import com.metis.runner.NodeRunner; +import com.metis.runner.RunnerResult; +import com.metis.runner.factory.NodeRunnerFactory; +import com.metis.utils.GenericInterfacesUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerService { + + private final AppEngineService appEngineService; + + + @Override + public RunnerResult running(FlowRunningContext context) { + App app = getApp(context); + Assert.isTrue(ObjectUtil.isNotNull(app), "app为空"); + // 构建运行实例, 并将运行实例放入上下文 + Long instanceId = IdUtil.getSnowflakeNextId(); + // 构建系统上下文信息 + SysContext sysContext = SysContext.builder() + .files(context.getFiles()) + .appId(app.getId()) + .workflowId(app.getWorkflowId()) + .instanceId(instanceId) + .build(); + // 构建运行中上下文 + RunningContext runningContext = RunningContext.buildContext(sysContext, context); + // 构建节点映射对象 + GraphDto graph = GraphDto.of(app.getGraph()); + Set readyRunningNode = new HashSet<>(); + + // 获取到开始节点 + // 开始节点为空,则表示数据存在异常 + Assert.isTrue(ObjectUtil.isNotNull(readyRunningNode), "流程图不存在开始节点"); + for (Node node : graph.getSortedNodes()) { + Long nodeId = node.getId(); + if (!graph.isNodeReady(nodeId)) { + continue; + } + log.info("当前运行节点 id:{}, name:{}, type:{}", node.getId(), node.getData().getLabel(), node.getType()); + // 当前节点接下来的连接线信息 + List edges = graph.getEdgeNodeId(nodeId); + // 执行 + NodeRunner nodeRunner = getNodeRunner(node); + node.setConfigClass(GenericInterfacesUtils.getClass(nodeRunner)); + // 下一个需要运行的节点id加入到可以运行的节点中 + // 获取到返回结果 + RunningResult result = nodeRunner.run(runningContext, node, edges); + log.info("节点执行结果:{}", JSON.toJSONString(result)); + // 节点执行结果参数放入上下文中 + if (ObjectUtil.isNotNull(result.getNodeContext())) { + runningContext.addNodeRunningContext(node.getId(), result.getNodeContext()); + } + // 下一个需要运行的节点id加入到可以运行的节点中 + if (CollUtil.isNotEmpty(result.getNextRunNodeId())) { + for (Long nextNodeId : result.getNextRunNodeId()) { + graph.updateNodeReadyMap(nextNodeId, true); + } + } else { + // 如果没有返回, 则认为所有的下级节点都需要运行 + edges.forEach(edge -> { + graph.updateNodeReadyMap(edge.getTarget(), true); + }); + } + } + Node endNode = graph.getEndNode(); + + JSONObject endRunningContext = runningContext.getRunningContext(endNode.getId()); + + return RunnerResult.builder() + .result(endRunningContext) + .context(sysContext) + .build(); + + } + + private void doRunning(Set readyRunningNode, Map> edgeMap, RunningContext runningContext) { + Set nextRunningNodeId = new HashSet<>(); + for (Node runningNode : readyRunningNode) { + // 当前节点接下来的连接线信息 + List edges = edgeMap.getOrDefault(runningNode.getId(), new ArrayList<>()); + // 执行 + NodeRunner nodeRunner = getNodeRunner(runningNode); + runningNode.setConfigClass(GenericInterfacesUtils.getClass(nodeRunner)); + // 获取到返回结果 + RunningResult result = nodeRunner.run(runningContext, runningNode, edges); + // 节点执行结果参数放入上下文中 + if (ObjectUtil.isNotNull(result.getNodeContext())) { + runningContext.addNodeRunningContext(runningNode.getId(), result.getNodeContext()); + } + if (CollUtil.isNotEmpty(result.getNextRunNodeId())) { + nextRunningNodeId.addAll(result.getNextRunNodeId()); + } + } + } + + + private RunningResult doRunning(RunningContext runningContext, Node node, List edges) { + NodeRunner nodeRunner = getNodeRunner(node); + node.setConfigClass(GenericInterfacesUtils.getClass(nodeRunner)); + // 获取到返回结果 + return nodeRunner.run(runningContext, node, edges); + } + + + /** + * 获取节点运行程序 + * + * @param node 节点 + * @return {@link NodeRunner } + */ + private NodeRunner getNodeRunner(Node node) { + if (NodeType.CUSTOM.equals(node.getType())) { + Assert.isTrue(StrUtil.isNotBlank(node.getCustomType()), "自定义节点类型不能为空"); + return NodeRunnerFactory.getCustom(node.getCustomType()); + } + return NodeRunnerFactory.get(node.getType()); + } + + + /** + * 获取到应用程序信息 + * + * @param context 上下文 + * @return {@link App } + */ + private App getApp(FlowRunningContext context) { + if (ObjectUtil.isNotNull(context.getWorkflowId())) { + return appEngineService.getByWorkflowId(context.getWorkflowId()); + } + return appEngineService.getByAppId(context.getAppId()); + } +} diff --git a/src/main/java/com/metis/enums/BaseEnum.java b/metis-starter/src/main/java/com/metis/enums/BaseEnum.java similarity index 100% rename from src/main/java/com/metis/enums/BaseEnum.java rename to metis-starter/src/main/java/com/metis/enums/BaseEnum.java diff --git a/src/main/java/com/metis/flow/enums/EdgeType.java b/metis-starter/src/main/java/com/metis/enums/EdgeType.java similarity index 96% rename from src/main/java/com/metis/flow/enums/EdgeType.java rename to metis-starter/src/main/java/com/metis/enums/EdgeType.java index 4113c13..0a53b1a 100644 --- a/src/main/java/com/metis/flow/enums/EdgeType.java +++ b/metis-starter/src/main/java/com/metis/enums/EdgeType.java @@ -1,4 +1,4 @@ -package com.metis.flow.enums; +package com.metis.enums; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/metis-starter/src/main/java/com/metis/enums/FileUploadType.java b/metis-starter/src/main/java/com/metis/enums/FileUploadType.java new file mode 100644 index 0000000..f1bde0a --- /dev/null +++ b/metis-starter/src/main/java/com/metis/enums/FileUploadType.java @@ -0,0 +1,45 @@ +package com.metis.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 文件上传类型 + * + * @author clay + * @date 2025/04/08 + */ +@Getter +@AllArgsConstructor +public enum FileUploadType { + LOCAL_FILE(1, "localFile", "本地文件"), + REMOTE_URL(2, "remoteUrl", "远程URL"); + + + private final Integer code; + + @JsonValue + private final String value; + + private final String name; + + + /** + * 枚举序列化器(前端传code时自动转换为对应枚举) + * + * @param value 值 + * @return 枚举 + */ + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static FileUploadType get(String value) { + return Arrays.stream(FileUploadType.class.getEnumConstants()) + .filter(e -> e.getValue().equals(value)) + .findFirst() + .orElse(null); + } + + } diff --git a/src/main/java/com/metis/flow/enums/HandleType.java b/metis-starter/src/main/java/com/metis/enums/HandleType.java similarity index 96% rename from src/main/java/com/metis/flow/enums/HandleType.java rename to metis-starter/src/main/java/com/metis/enums/HandleType.java index 37dc51e..227ac69 100644 --- a/src/main/java/com/metis/flow/enums/HandleType.java +++ b/metis-starter/src/main/java/com/metis/enums/HandleType.java @@ -1,4 +1,4 @@ -package com.metis.flow.enums; +package com.metis.enums; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/src/main/java/com/metis/flow/enums/NodeType.java b/metis-starter/src/main/java/com/metis/enums/NodeType.java similarity index 70% rename from src/main/java/com/metis/flow/enums/NodeType.java rename to metis-starter/src/main/java/com/metis/enums/NodeType.java index 0c16ad4..89717e7 100644 --- a/src/main/java/com/metis/flow/enums/NodeType.java +++ b/metis-starter/src/main/java/com/metis/enums/NodeType.java @@ -1,4 +1,4 @@ -package com.metis.flow.enums; +package com.metis.enums; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; @@ -11,8 +11,14 @@ import java.util.Arrays; @AllArgsConstructor public enum NodeType { - START(1, "start", "开始", Object.class), - END(2, "end", "结束", Object.class), + START(1, "start", "开始"), + END(2, "end", "结束"), + DOCUMENT_EXTRACTOR(3, "documentExtractor", "文档提取器"), + CUSTOM(4, "custom", "自定义节点"), + LLM(5, "llm", "LLM"), + QUESTION_CLASSIFIER(6, "questionClassifier", "问题分类器"), + IF_ELSE(7, "ifElse", "条件判断"), + ; @@ -24,8 +30,6 @@ public enum NodeType { private final String name; - private final Class configClass; - /** * 枚举序列化器(前端传code时自动转换为对应枚举) diff --git a/metis-starter/src/main/java/com/metis/enums/NodeVariableType.java b/metis-starter/src/main/java/com/metis/enums/NodeVariableType.java new file mode 100644 index 0000000..09ca06d --- /dev/null +++ b/metis-starter/src/main/java/com/metis/enums/NodeVariableType.java @@ -0,0 +1,50 @@ +package com.metis.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 节点变量类型 + * + * @author clay + * @date 2025/04/08 + */ +@Getter +@AllArgsConstructor +public enum NodeVariableType { + TEXT_INPUT(1, "text-input", "文本"), + PARAGRAPH(2, "paragraph", "段落"), + SELECT(3, "select", "下拉框"), + NUMBER(4, "number", "数字"), + FILE(5, "file", "文件"), + FILE_LIST(6, "file-list", "文件列表") + ; + + + private final Integer code; + + @JsonValue + private final String value; + + private final String name; + + + /** + * 枚举序列化器(前端传code时自动转换为对应枚举) + * + * @param value 值 + * @return 枚举 + */ + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static NodeVariableType get(String value) { + return Arrays.stream(NodeVariableType.class.getEnumConstants()) + .filter(e -> e.getValue().equals(value)) + .findFirst() + .orElse(null); + } + +} diff --git a/src/main/java/com/metis/flow/enums/PositionType.java b/metis-starter/src/main/java/com/metis/enums/PositionType.java similarity index 97% rename from src/main/java/com/metis/flow/enums/PositionType.java rename to metis-starter/src/main/java/com/metis/enums/PositionType.java index 98eb1d0..a5d67a1 100644 --- a/src/main/java/com/metis/flow/enums/PositionType.java +++ b/metis-starter/src/main/java/com/metis/enums/PositionType.java @@ -1,4 +1,4 @@ -package com.metis.flow.enums; +package com.metis.enums; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/src/main/java/com/metis/enums/ResultEnum.java b/metis-starter/src/main/java/com/metis/enums/ResultEnum.java similarity index 100% rename from src/main/java/com/metis/enums/ResultEnum.java rename to metis-starter/src/main/java/com/metis/enums/ResultEnum.java diff --git a/src/main/java/com/metis/enums/YesOrNoEnum.java b/metis-starter/src/main/java/com/metis/enums/YesOrNoEnum.java similarity index 100% rename from src/main/java/com/metis/enums/YesOrNoEnum.java rename to metis-starter/src/main/java/com/metis/enums/YesOrNoEnum.java diff --git a/src/main/java/com/metis/facade/ProcessDefinitionFacade.java b/metis-starter/src/main/java/com/metis/facade/ProcessDefinitionFacade.java similarity index 62% rename from src/main/java/com/metis/facade/ProcessDefinitionFacade.java rename to metis-starter/src/main/java/com/metis/facade/ProcessDefinitionFacade.java index 5272714..7926301 100644 --- a/src/main/java/com/metis/facade/ProcessDefinitionFacade.java +++ b/metis-starter/src/main/java/com/metis/facade/ProcessDefinitionFacade.java @@ -1,10 +1,12 @@ package com.metis.facade; +import com.metis.convert.GraphConvert; +import com.metis.domain.bo.CreateApp; import com.metis.domain.bo.ProcessBo; -import com.metis.flow.domain.entity.App; -import com.metis.flow.domain.bo.CreateApp; -import com.metis.flow.domain.bo.UpdateApp; -import com.metis.flow.engine.AppEngineService; +import com.metis.domain.bo.UpdateApp; +import com.metis.domain.entity.App; +import com.metis.domain.entity.base.Graph; +import com.metis.engine.AppEngineService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -21,24 +23,27 @@ public class ProcessDefinitionFacade { * * @param processBo 过程业务对象 */ - public void create(ProcessBo processBo) { + public Long create(ProcessBo processBo) { + Graph graph = GraphConvert.INSTANCE.toEntity(processBo.getGraph()); CreateApp createApp = CreateApp.builder() .name(processBo.getName()) - .graph(processBo.getGraph()) + .graph(graph) .build(); App app = appEngineService.create(createApp); + return app.getWorkflowId(); } public App getByDeploymentId(Long deploymentId) { - return appEngineService.getByDeploymentId(deploymentId); + return appEngineService.getByWorkflowId(deploymentId); } public void update(ProcessBo processBo) { + Graph graph = GraphConvert.INSTANCE.toEntity(processBo.getGraph()); appEngineService.update(UpdateApp.builder() .defaultUse(processBo.getDefaultUse()) .appId(processBo.getAppId()) .name(processBo.getName()) - .graph(processBo.getGraph()) + .graph(graph) .build()); } diff --git a/src/main/java/com/metis/handle/GlobalExceptionHandler.java b/metis-starter/src/main/java/com/metis/handle/GlobalExceptionHandler.java similarity index 100% rename from src/main/java/com/metis/handle/GlobalExceptionHandler.java rename to metis-starter/src/main/java/com/metis/handle/GlobalExceptionHandler.java diff --git a/src/main/java/com/metis/flow/mapper/BaseAppMapper.java b/metis-starter/src/main/java/com/metis/mapper/BaseAppMapper.java similarity index 76% rename from src/main/java/com/metis/flow/mapper/BaseAppMapper.java rename to metis-starter/src/main/java/com/metis/mapper/BaseAppMapper.java index cb09b20..e9b88fc 100644 --- a/src/main/java/com/metis/flow/mapper/BaseAppMapper.java +++ b/metis-starter/src/main/java/com/metis/mapper/BaseAppMapper.java @@ -1,6 +1,6 @@ -package com.metis.flow.mapper; +package com.metis.mapper; -import com.metis.flow.domain.entity.BaseApp; +import com.metis.domain.entity.BaseApp; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; diff --git a/src/main/java/com/metis/mybatis/MybatisPlusConfiguration.java b/metis-starter/src/main/java/com/metis/mybatis/MybatisPlusConfiguration.java similarity index 100% rename from src/main/java/com/metis/mybatis/MybatisPlusConfiguration.java rename to metis-starter/src/main/java/com/metis/mybatis/MybatisPlusConfiguration.java diff --git a/src/main/java/com/metis/mybatis/handler/BaseEntityMetaObjectHandler.java b/metis-starter/src/main/java/com/metis/mybatis/handler/BaseEntityMetaObjectHandler.java similarity index 100% rename from src/main/java/com/metis/mybatis/handler/BaseEntityMetaObjectHandler.java rename to metis-starter/src/main/java/com/metis/mybatis/handler/BaseEntityMetaObjectHandler.java diff --git a/src/main/java/com/metis/mybatis/logic/SelectIgnoreLogicDelete.java b/metis-starter/src/main/java/com/metis/mybatis/logic/SelectIgnoreLogicDelete.java similarity index 100% rename from src/main/java/com/metis/mybatis/logic/SelectIgnoreLogicDelete.java rename to metis-starter/src/main/java/com/metis/mybatis/logic/SelectIgnoreLogicDelete.java diff --git a/src/main/java/com/metis/mybatis/support/CustomSqlInjector.java b/metis-starter/src/main/java/com/metis/mybatis/support/CustomSqlInjector.java similarity index 100% rename from src/main/java/com/metis/mybatis/support/CustomSqlInjector.java rename to metis-starter/src/main/java/com/metis/mybatis/support/CustomSqlInjector.java diff --git a/src/main/java/com/metis/result/Result.java b/metis-starter/src/main/java/com/metis/result/Result.java similarity index 100% rename from src/main/java/com/metis/result/Result.java rename to metis-starter/src/main/java/com/metis/result/Result.java diff --git a/src/main/java/com/metis/result/page/TableDataInfo.java b/metis-starter/src/main/java/com/metis/result/page/TableDataInfo.java similarity index 100% rename from src/main/java/com/metis/result/page/TableDataInfo.java rename to metis-starter/src/main/java/com/metis/result/page/TableDataInfo.java diff --git a/metis-starter/src/main/java/com/metis/runner/CustomNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/CustomNodeRunner.java new file mode 100644 index 0000000..c0b7143 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/CustomNodeRunner.java @@ -0,0 +1,33 @@ +package com.metis.runner; + +import com.metis.domain.entity.base.NodeConfig; +import com.metis.enums.NodeType; + +/** + * 自定义节点运行器 + * + * @author clay + * @date 2025/04/20 + */ +public interface CustomNodeRunner extends NodeRunner { + + + /** + * 获取自定义节点的节点类型 + * + * @return {@link String } + */ + String getCustomNodeType(); + + + /** + * 得到类型 + * + * @return {@link NodeType } + */ + default NodeType getType() { + return NodeType.CUSTOM; + } + + +} diff --git a/metis-starter/src/main/java/com/metis/runner/FlowRunningContext.java b/metis-starter/src/main/java/com/metis/runner/FlowRunningContext.java new file mode 100644 index 0000000..022f693 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/FlowRunningContext.java @@ -0,0 +1,49 @@ +package com.metis.runner; + +import com.alibaba.fastjson2.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 运行上下文 + * + * @author clay + * @date 2025/04/07 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FlowRunningContext { + + /** + * 文件列表 + */ + private List files; + + /** + * 应用程序id + */ + private Long appId; + + /** + * 用户id + */ + private Long userId; + + /** + * 工作流id + */ + private Long workflowId; + + /** + * 自定义 + */ + private JSONObject custom; + + +} diff --git a/metis-starter/src/main/java/com/metis/runner/NodeRunner.java b/metis-starter/src/main/java/com/metis/runner/NodeRunner.java new file mode 100644 index 0000000..cd59b92 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/NodeRunner.java @@ -0,0 +1,56 @@ +package com.metis.runner; + +import cn.hutool.core.collection.CollUtil; +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.base.NodeConfig; +import com.metis.enums.NodeType; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 内置节点运行器 + * + * @author clay + * @date 2025/04/20 + */ +public interface NodeRunner { + + + /** + * 运行 + * + * @param context 上下文 + * @param node 节点配置信息 + * @param edges 当前接下来的连线信息, 一些特殊节点需要节点内部判断下一个运行节点 + * @return {@link RunningContext } + */ + RunningResult run(RunningContext context, Node node, List edges); + + + /** + * 获取节点类型 + * + * @return {@link NodeType } + */ + NodeType getType(); + + + /** + * 获取下一个节点id + * + * @param edges 边缘 + * @return {@link Set }<{@link Long }> + */ + default Set getNextNodeIds(List edges) { + if (CollUtil.isEmpty(edges)) { + return Set.of(); + } + return edges.stream().map(Edge::getTarget).collect(Collectors.toSet()); + } + +} diff --git a/metis-starter/src/main/java/com/metis/runner/RunnerResult.java b/metis-starter/src/main/java/com/metis/runner/RunnerResult.java new file mode 100644 index 0000000..3306ea5 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/RunnerResult.java @@ -0,0 +1,32 @@ +package com.metis.runner; + + +import com.alibaba.fastjson2.JSONObject; +import com.metis.domain.context.SysContext; +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +/** + * 运行结果 + * + * @author clay + * @date 2025/04/07 + */ +@Data +@Builder +public class RunnerResult { + + /** + * 运行内容 + */ + private JSONObject result; + + /** + * 上下文 + */ + private SysContext context; + + +} diff --git a/metis-starter/src/main/java/com/metis/runner/factory/NodeRunnerFactory.java b/metis-starter/src/main/java/com/metis/runner/factory/NodeRunnerFactory.java new file mode 100644 index 0000000..a2204fa --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/factory/NodeRunnerFactory.java @@ -0,0 +1,64 @@ +package com.metis.runner.factory; + +import cn.hutool.core.lang.Assert; +import com.metis.enums.NodeType; +import com.metis.runner.CustomNodeRunner; +import com.metis.runner.NodeRunner; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class NodeRunnerFactory { + + /** + * 内置节点运行器 + */ + private static final Map NODE_MAP = new ConcurrentHashMap<>(8); + /** + * 自定义节点映射 + */ + private static final Map CUSTOM_NODE_MAP = new ConcurrentHashMap<>(8); + + /** + * 注册 + * + * @param runner 跑步者 + */ + static void register(NodeRunner runner) { + NODE_MAP.put(runner.getType(), runner); + } + + + /** + * 得到 + * + * @param type 类型 + * @return {@link NodeRunner } + */ + public static NodeRunner get(NodeType type) { + return NODE_MAP.get(type); + } + + + /** + * 注册自定义节点 + * + * @param runner 跑步者 + */ + static void registerCustom(CustomNodeRunner runner) { + Assert.isTrue(!CUSTOM_NODE_MAP.containsKey(runner.getCustomNodeType()), "已存在类型:{}, class:{}的运行器", runner.getCustomNodeType(), runner.getClass()); + CUSTOM_NODE_MAP.put(runner.getCustomNodeType(), runner); + } + + /** + * 得到自定义节点运行器 + * + * @param type 类型 + * @return {@link NodeRunner } + */ + public static NodeRunner getCustom(String type) { + return CUSTOM_NODE_MAP.get(type); + } + + +} diff --git a/metis-starter/src/main/java/com/metis/runner/factory/RunnerInitialize.java b/metis-starter/src/main/java/com/metis/runner/factory/RunnerInitialize.java new file mode 100644 index 0000000..b02873a --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/factory/RunnerInitialize.java @@ -0,0 +1,38 @@ +package com.metis.runner.factory; + +import cn.hutool.core.lang.Assert; +import com.metis.enums.NodeType; +import com.metis.runner.CustomNodeRunner; +import com.metis.runner.NodeRunner; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * runner初始化 + * + * @author clay + * @date 2025/04/07 + */ +@Service +public class RunnerInitialize implements ApplicationContextAware { + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + Map runnerMap = applicationContext.getBeansOfType(NodeRunner.class); + + runnerMap.forEach((runnerBeanName, runner) -> { + if (NodeType.CUSTOM.equals(runner.getType())) { + Assert.isTrue(runner instanceof CustomNodeRunner, "自定义节点必须实现CustomNodeRunner接口"); + NodeRunnerFactory.registerCustom((CustomNodeRunner) runner); + } else { + NodeRunnerFactory.register(runner); + } + }); + } + +} diff --git a/metis-starter/src/main/java/com/metis/runner/impl/EndNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/EndNodeRunner.java new file mode 100644 index 0000000..4aacda8 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/impl/EndNodeRunner.java @@ -0,0 +1,33 @@ +package com.metis.runner.impl; + + +import com.alibaba.fastjson2.JSONObject; +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.EndNodeConfig; +import com.metis.enums.NodeType; +import com.metis.runner.NodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class EndNodeRunner implements NodeRunner { + + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + JSONObject contextNodeValue = new JSONObject(); + contextNodeValue.put("userId", context.getSys().getAppId()); + return RunningResult.buildResult(contextNodeValue); + } + + @Override + public NodeType getType() { + return NodeType.END; + } + +} diff --git a/metis-starter/src/main/java/com/metis/runner/impl/LLMNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/LLMNodeRunner.java new file mode 100644 index 0000000..1779a34 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/impl/LLMNodeRunner.java @@ -0,0 +1,27 @@ +package com.metis.runner.impl; + +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.LLMNodeConfig; +import com.metis.enums.NodeType; +import com.metis.runner.NodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class LLMNodeRunner implements NodeRunner { + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + return RunningResult.buildResult(); + } + + @Override + public NodeType getType() { + return NodeType.LLM; + } +} diff --git a/metis-starter/src/main/java/com/metis/runner/impl/QuestionClassifierRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/QuestionClassifierRunner.java new file mode 100644 index 0000000..d23f7a5 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/impl/QuestionClassifierRunner.java @@ -0,0 +1,36 @@ +package com.metis.runner.impl; + +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.QuestionClassifierConfig; +import com.metis.enums.NodeType; +import com.metis.runner.NodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +@Slf4j +@Service +public class QuestionClassifierRunner implements NodeRunner { + + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + Set nextNodeIds = getNextNodeIds(edges); + // 生成随机索引 + Random random = new Random(); + int randomIndex = random.nextInt(nextNodeIds.size()); + List nodeIds = new ArrayList<>(nextNodeIds); + return RunningResult.buildResult(Set.of(nodeIds.get(randomIndex))); + } + + @Override + public NodeType getType() { + return NodeType.QUESTION_CLASSIFIER; + } +} diff --git a/metis-starter/src/main/java/com/metis/runner/impl/StartNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/StartNodeRunner.java new file mode 100644 index 0000000..7019f92 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/impl/StartNodeRunner.java @@ -0,0 +1,52 @@ +package com.metis.runner.impl; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson2.JSONObject; +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.base.NodeVariable; +import com.metis.domain.entity.config.node.StartNodeConfig; +import com.metis.enums.NodeType; +import com.metis.runner.NodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 启动节点运行器, 开始节点功能主要为将用户传入的参数进行校验, 并放入到上下文中 + * + * @author clay + * @date 2025/04/08 + */ +@Slf4j +@Service +public class StartNodeRunner implements NodeRunner { + + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + StartNodeConfig config = node.getConfig(); + // 获取到节点的自定义参数 + List variables = config.getVariables(); + // 如果没有自定义参数, 则直接返回 + if (CollUtil.isEmpty(variables)) { + return RunningResult.buildResult(); + } + // 获取用户自定义参数 + JSONObject custom = context.getCustom(); + JSONObject contextNodeValue = new JSONObject(); + for (NodeVariable variable : variables) { + Object value = variable.getValue(custom); + contextNodeValue.put(variable.getVariable(), value); + } + return RunningResult.buildResult(contextNodeValue); + } + + + @Override + public NodeType getType() { + return NodeType.START; + } +} diff --git a/src/main/java/com/metis/flow/service/BaseAppService.java b/metis-starter/src/main/java/com/metis/service/BaseAppService.java similarity index 92% rename from src/main/java/com/metis/flow/service/BaseAppService.java rename to metis-starter/src/main/java/com/metis/service/BaseAppService.java index 2fa231b..96d1c31 100644 --- a/src/main/java/com/metis/flow/service/BaseAppService.java +++ b/metis-starter/src/main/java/com/metis/service/BaseAppService.java @@ -1,8 +1,8 @@ -package com.metis.flow.service; +package com.metis.service; import com.metis.enums.YesOrNoEnum; -import com.metis.flow.domain.query.AppQuery; -import com.metis.flow.domain.entity.BaseApp; +import com.metis.domain.query.AppQuery; +import com.metis.domain.entity.BaseApp; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; diff --git a/src/main/java/com/metis/flow/service/impl/BaseAppServiceImpl.java b/metis-starter/src/main/java/com/metis/service/impl/BaseAppServiceImpl.java similarity index 93% rename from src/main/java/com/metis/flow/service/impl/BaseAppServiceImpl.java rename to metis-starter/src/main/java/com/metis/service/impl/BaseAppServiceImpl.java index 5cbe09a..c9263ab 100644 --- a/src/main/java/com/metis/flow/service/impl/BaseAppServiceImpl.java +++ b/metis-starter/src/main/java/com/metis/service/impl/BaseAppServiceImpl.java @@ -1,4 +1,4 @@ -package com.metis.flow.service.impl; +package com.metis.service.impl; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -6,10 +6,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.metis.enums.YesOrNoEnum; -import com.metis.flow.domain.query.AppQuery; -import com.metis.flow.domain.entity.BaseApp; -import com.metis.flow.mapper.BaseAppMapper; -import com.metis.flow.service.BaseAppService; +import com.metis.domain.query.AppQuery; +import com.metis.domain.entity.BaseApp; +import com.metis.mapper.BaseAppMapper; +import com.metis.service.BaseAppService; import com.metis.utils.PageConditionUtil; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/metis/sseclient/ToolSpecificationHelper.java b/metis-starter/src/main/java/com/metis/sseclient/ToolSpecificationHelper.java similarity index 100% rename from src/main/java/com/metis/sseclient/ToolSpecificationHelper.java rename to metis-starter/src/main/java/com/metis/sseclient/ToolSpecificationHelper.java diff --git a/src/main/java/com/metis/sseclient/check/SseCheck.java b/metis-starter/src/main/java/com/metis/sseclient/check/SseCheck.java similarity index 100% rename from src/main/java/com/metis/sseclient/check/SseCheck.java rename to metis-starter/src/main/java/com/metis/sseclient/check/SseCheck.java diff --git a/src/main/java/com/metis/sseclient/event/SseEventListener.java b/metis-starter/src/main/java/com/metis/sseclient/event/SseEventListener.java similarity index 100% rename from src/main/java/com/metis/sseclient/event/SseEventListener.java rename to metis-starter/src/main/java/com/metis/sseclient/event/SseEventListener.java diff --git a/src/main/java/com/metis/sseclient/handler/McpOperationHandler.java b/metis-starter/src/main/java/com/metis/sseclient/handler/McpOperationHandler.java similarity index 100% rename from src/main/java/com/metis/sseclient/handler/McpOperationHandler.java rename to metis-starter/src/main/java/com/metis/sseclient/handler/McpOperationHandler.java diff --git a/metis-starter/src/main/java/com/metis/utils/GenericInterfacesUtils.java b/metis-starter/src/main/java/com/metis/utils/GenericInterfacesUtils.java new file mode 100644 index 0000000..c8b14f8 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/utils/GenericInterfacesUtils.java @@ -0,0 +1,25 @@ +package com.metis.utils; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class GenericInterfacesUtils { + + + public static Class getClass(Object object) { + + Type[] genericInterfaces = object.getClass().getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericInterface; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length > 0) { + return (Class) typeArguments[0]; + } + } + } + throw new IllegalStateException("无法获取泛型类型"); + } + + +} diff --git a/src/main/java/com/metis/utils/LocalDateTimeUtils.java b/metis-starter/src/main/java/com/metis/utils/LocalDateTimeUtils.java similarity index 100% rename from src/main/java/com/metis/utils/LocalDateTimeUtils.java rename to metis-starter/src/main/java/com/metis/utils/LocalDateTimeUtils.java diff --git a/src/main/java/com/metis/utils/PageConditionUtil.java b/metis-starter/src/main/java/com/metis/utils/PageConditionUtil.java similarity index 100% rename from src/main/java/com/metis/utils/PageConditionUtil.java rename to metis-starter/src/main/java/com/metis/utils/PageConditionUtil.java diff --git a/src/main/java/com/metis/utils/PageInfo.java b/metis-starter/src/main/java/com/metis/utils/PageInfo.java similarity index 100% rename from src/main/java/com/metis/utils/PageInfo.java rename to metis-starter/src/main/java/com/metis/utils/PageInfo.java diff --git a/src/main/java/com/metis/utils/TableSupport.java b/metis-starter/src/main/java/com/metis/utils/TableSupport.java similarity index 100% rename from src/main/java/com/metis/utils/TableSupport.java rename to metis-starter/src/main/java/com/metis/utils/TableSupport.java diff --git a/src/main/java/com/metis/utils/TransactionalUtils.java b/metis-starter/src/main/java/com/metis/utils/TransactionalUtils.java similarity index 100% rename from src/main/java/com/metis/utils/TransactionalUtils.java rename to metis-starter/src/main/java/com/metis/utils/TransactionalUtils.java diff --git a/metis-starter/src/main/java/com/metis/validator/CustomNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/CustomNodeValidator.java new file mode 100644 index 0000000..ee37924 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/CustomNodeValidator.java @@ -0,0 +1,34 @@ +package com.metis.validator; + +import com.metis.domain.entity.base.NodeConfig; +import com.metis.enums.NodeType; + +/** + * 自定义节点验证器 + * + * @author clay + * @date 2025/04/20 + */ +public interface CustomNodeValidator extends NodeValidator { + + + /** + * 获取自定义节点的节点类型 + * + * @return {@link String } + */ + String getCustomNodeType(); + + + /** + * 得到类型 + * + * @return {@link NodeType } + */ + default NodeType getType() { + return NodeType.CUSTOM; + } + + + +} diff --git a/src/main/java/com/metis/flow/validator/EdgeValidator.java b/metis-starter/src/main/java/com/metis/validator/EdgeValidator.java similarity index 70% rename from src/main/java/com/metis/flow/validator/EdgeValidator.java rename to metis-starter/src/main/java/com/metis/validator/EdgeValidator.java index a02d1e2..74f87b7 100644 --- a/src/main/java/com/metis/flow/validator/EdgeValidator.java +++ b/metis-starter/src/main/java/com/metis/validator/EdgeValidator.java @@ -1,7 +1,7 @@ -package com.metis.flow.validator; +package com.metis.validator; -import com.metis.flow.domain.entity.base.Edge; -import com.metis.flow.enums.EdgeType; +import com.metis.domain.entity.base.Edge; +import com.metis.enums.EdgeType; public interface EdgeValidator { diff --git a/metis-starter/src/main/java/com/metis/validator/NodeValidator.java b/metis-starter/src/main/java/com/metis/validator/NodeValidator.java new file mode 100644 index 0000000..27aac24 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/NodeValidator.java @@ -0,0 +1,40 @@ +package com.metis.validator; + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.base.NodeConfig; +import com.metis.enums.NodeType; + +import java.util.List; + +public interface NodeValidator { + + + /** + * 节点验证 + * + * @param node 节点 + * @return {@link ValidatorResult } + */ + ValidatorResult validateValue(Node node); + + + /** + * 验证关系 + * + * @param node 节点 + * @param sources 来源 + * @param targets 目标 + * @return {@link ValidatorResult } + */ + ValidatorResult validateRelation(Node node, List sources, List targets); + + + /** + * 得到类型 + * + * @return {@link NodeType } + */ + NodeType getType(); + +} diff --git a/metis-starter/src/main/java/com/metis/validator/ValidatorCodeService.java b/metis-starter/src/main/java/com/metis/validator/ValidatorCodeService.java new file mode 100644 index 0000000..a05e326 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/ValidatorCodeService.java @@ -0,0 +1,65 @@ +package com.metis.validator; + +import cn.hutool.core.collection.CollUtil; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ValidatorCodeService { + + private final Validator globalValidator; + + /** + * 验证, 出现校验未通过则抛出异常 + * + * @param validObject 有效对象 + */ + public void validateThrow(T validObject) { + // validation 编程式校验 + Set> validates = globalValidator.validate(validObject); + if (CollUtil.isNotEmpty(validates)) { + List errorMessage = validates.stream() + .map(ConstraintViolation::getMessage).toList(); + throw new RuntimeException(String.join(",", errorMessage)); + } + } + + /** + * 验证错误信息 + * + * @param validObject 有效对象 + * @return {@link String } + */ + public String validateErrorMsg(T validObject) { + // validation 编程式校验 + Set> validates = globalValidator.validate(validObject); + if (CollUtil.isNotEmpty(validates)) { + List errorMessage = validates.stream() + .map(ConstraintViolation::getMessage).toList(); + return String.join(",", errorMessage); + } + return null; + } + + + /** + * 验证 + * + * @param validObject 有效对象 + * @return boolean + */ + public boolean validate(T validObject) { + // validation 编程式校验 + Set> validates = globalValidator.validate(validObject); + return CollUtil.isEmpty(validates); + } + +} diff --git a/src/main/java/com/metis/flow/validator/ValidatorResult.java b/metis-starter/src/main/java/com/metis/validator/ValidatorResult.java similarity index 93% rename from src/main/java/com/metis/flow/validator/ValidatorResult.java rename to metis-starter/src/main/java/com/metis/validator/ValidatorResult.java index 8666b6e..a9e502a 100644 --- a/src/main/java/com/metis/flow/validator/ValidatorResult.java +++ b/metis-starter/src/main/java/com/metis/validator/ValidatorResult.java @@ -1,4 +1,4 @@ -package com.metis.flow.validator; +package com.metis.validator; import lombok.Data; diff --git a/src/main/java/com/metis/flow/validator/ValidatorService.java b/metis-starter/src/main/java/com/metis/validator/ValidatorService.java similarity index 66% rename from src/main/java/com/metis/flow/validator/ValidatorService.java rename to metis-starter/src/main/java/com/metis/validator/ValidatorService.java index ac5f11f..6cf5afb 100644 --- a/src/main/java/com/metis/flow/validator/ValidatorService.java +++ b/metis-starter/src/main/java/com/metis/validator/ValidatorService.java @@ -1,6 +1,6 @@ -package com.metis.flow.validator; +package com.metis.validator; -import com.metis.flow.domain.bo.BuildApp; +import com.metis.domain.bo.BuildApp; public interface ValidatorService { diff --git a/src/main/java/com/metis/flow/validator/factory/EdgeValidatorFactory.java b/metis-starter/src/main/java/com/metis/validator/factory/EdgeValidatorFactory.java similarity index 81% rename from src/main/java/com/metis/flow/validator/factory/EdgeValidatorFactory.java rename to metis-starter/src/main/java/com/metis/validator/factory/EdgeValidatorFactory.java index d3e357f..03c2eab 100644 --- a/src/main/java/com/metis/flow/validator/factory/EdgeValidatorFactory.java +++ b/metis-starter/src/main/java/com/metis/validator/factory/EdgeValidatorFactory.java @@ -1,7 +1,7 @@ -package com.metis.flow.validator.factory; +package com.metis.validator.factory; -import com.metis.flow.enums.EdgeType; -import com.metis.flow.validator.EdgeValidator; +import com.metis.enums.EdgeType; +import com.metis.validator.EdgeValidator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/metis-starter/src/main/java/com/metis/validator/factory/NodeValidatorFactory.java b/metis-starter/src/main/java/com/metis/validator/factory/NodeValidatorFactory.java new file mode 100644 index 0000000..14a501b --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/factory/NodeValidatorFactory.java @@ -0,0 +1,56 @@ +package com.metis.validator.factory; + +import cn.hutool.core.lang.Assert; +import com.metis.enums.NodeType; +import com.metis.validator.CustomNodeValidator; +import com.metis.validator.NodeValidator; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public final class NodeValidatorFactory { + + /** + * 内置节点验证器 + */ + private static final Map NODE_MAP = new ConcurrentHashMap<>(8); + /** + * 自定义节点验证器 + */ + private static final Map CUSTOM_NODE_MAP = new ConcurrentHashMap<>(8); + + + /** + * 注册 + * + * @param validator 验证器 + */ + static void register(NodeValidator validator) { + NODE_MAP.put(validator.getType(), validator); + } + + /** + * 得到 + * + * @param type 类型 + * @return {@link NodeValidator } + */ + public static NodeValidator get(NodeType type) { + return NODE_MAP.get(type); + } + + /** + * @param validator 验证器 + */ + static void registerCustom(CustomNodeValidator validator) { + Assert.isTrue(!CUSTOM_NODE_MAP.containsKey(validator.getCustomNodeType()), "已存在类型:{}, class:{}的验证器", validator.getCustomNodeType(), validator.getClass()); + CUSTOM_NODE_MAP.put(validator.getCustomNodeType(), validator); + } + + public static NodeValidator getCustom(String type) { + return CUSTOM_NODE_MAP.get(type); + } + + +} diff --git a/metis-starter/src/main/java/com/metis/validator/factory/ValidatorInitialize.java b/metis-starter/src/main/java/com/metis/validator/factory/ValidatorInitialize.java new file mode 100644 index 0000000..40b92fe --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/factory/ValidatorInitialize.java @@ -0,0 +1,43 @@ +package com.metis.validator.factory; + + +import cn.hutool.core.lang.Assert; +import com.metis.enums.NodeType; +import com.metis.validator.CustomNodeValidator; +import com.metis.validator.EdgeValidator; +import com.metis.validator.NodeValidator; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class ValidatorInitialize implements ApplicationContextAware { + + + @Override + public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { + + Map edgeMap = applicationContext.getBeansOfType(EdgeValidator.class); + + edgeMap.forEach((edgeValidatorBeanName, edgeValidator) -> { + EdgeValidatorFactory.register(edgeValidator); + }); + + Map nodeMap = applicationContext.getBeansOfType(NodeValidator.class); + + nodeMap.forEach((nodeValidatorBeanName, nodeValidator) -> { + if (NodeType.CUSTOM.equals(nodeValidator.getType())) { + Assert.isTrue(nodeValidator instanceof CustomNodeValidator, "自定义节点必须实现CustomNodeValidator接口"); + NodeValidatorFactory.registerCustom((CustomNodeValidator) nodeValidator); + } else { + NodeValidatorFactory.register(nodeValidator); + } + }); + } + + +} diff --git a/metis-starter/src/main/java/com/metis/validator/impl/ValidatorServiceImpl.java b/metis-starter/src/main/java/com/metis/validator/impl/ValidatorServiceImpl.java new file mode 100644 index 0000000..bd42907 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/ValidatorServiceImpl.java @@ -0,0 +1,268 @@ +package com.metis.validator.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.metis.domain.bo.BuildApp; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Graph; +import com.metis.domain.entity.base.Node; +import com.metis.enums.EdgeType; +import com.metis.enums.NodeType; +import com.metis.validator.*; +import com.metis.validator.factory.EdgeValidatorFactory; +import com.metis.validator.factory.NodeValidatorFactory; +import com.metis.utils.GenericInterfacesUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ValidatorServiceImpl implements ValidatorService { + + private final ValidatorCodeService validatorCodeService; + + @Override + public void validate(BuildApp buildApp) { + // validation 编程式校验 + validatorCodeService.validateThrow(buildApp); + Graph graph = buildApp.getGraph(); + // 节点参数校验 + validateNode(graph.getNodes()); + // 线参数校验 + validateEdge(graph.getEdges()); + // 关系验证 + validateRelation(graph.getNodes(), graph.getEdges()); + } + + + /** + * 验证节点 + * + * @param nodes 节点 + */ + private void validateNode(List nodes) { + List errorMessage = new ArrayList<>(); + for (Node node : nodes) { + NodeType type = node.getType(); + NodeValidator validator = getNodeValidator(node); + node.setConfigClass(GenericInterfacesUtils.getClass(validator)); + // 节点校验器 + Assert.isTrue(ObjectUtil.isNotNull(validator), "无:{}类型的节点校验器", type.getName()); + ValidatorResult result = validator.validateValue(node); + // 返回值检查 + Assert.isTrue(ObjectUtil.isNotNull(result), "类型:{} 的校验器无返回值", validator.getType().getName()); + if (!result.getValid()) { + errorMessage.add(result.getMessage()); + } + } + Assert.isTrue(CollUtil.isEmpty(errorMessage), String.join(",", errorMessage)); + } + + + /** + * 验证边缘 + * + * @param edges 线 + */ + private void validateEdge(List edges) { + List errorMessage = new ArrayList<>(); + for (Edge edge : edges) { + EdgeType type = edge.getType(); + EdgeValidator validator = EdgeValidatorFactory.get(type); + // 节点校验器 + Assert.isTrue(ObjectUtil.isNotNull(validator), "无:{}类型的边校验器", type.getName()); + ValidatorResult result = validator.validate(edge); + // 返回值检查 + Assert.isTrue(ObjectUtil.isNotNull(result), "类型:{} 的校验器无返回值", validator.getType().getName()); + if (!result.getValid()) { + errorMessage.add(result.getMessage()); + } + } + Assert.isTrue(CollUtil.isEmpty(errorMessage), String.join(",", errorMessage)); + } + + + /** + * 验证关系 + * + * @param nodes 节点 + * @param edges 边缘 + */ + private void validateRelation(List nodes, List edges) { + // 0. 检查是否只有一个起始节点 + validateSingleStartNode(nodes); + + // 1. 检查线是否连接有效节点 + validateEdgeConnections(nodes, edges); + + // 2. 检查是否存在环结构 + validateCycle(nodes, edges); + + // 3. 检查是否存在孤立节点 + validateIsolatedNodes(nodes, edges); + + // 4. 检查两个节点间是否有多根连线 + validateMultipleEdgesBetweenNodes(edges); + + // 5. 检查节点自己的关系信息 + validateNodeRelations(nodes, edges); + + } + + /** + * 检查是否只有一个起始节点 + * + * @param nodes 节点 + */ + private void validateSingleStartNode(List nodes) { + Long startNodeCount = nodes.stream() + .filter(node -> NodeType.START.equals(node.getType())) + .count(); + Assert.isTrue(startNodeCount.equals(1L), "图中必须且只能有一个起始节点,当前起始节点数量: {}", startNodeCount); + } + + + /** + * 检查线是否连接有效节点 + * + * @param nodes 节点 + * @param edges 边缘 + */ + private void validateEdgeConnections(List nodes, List edges) { + Map nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, Function.identity())); + for (Edge edge : edges) { + Long source = edge.getSource(); + Long target = edge.getTarget(); + Assert.isTrue(nodeMap.containsKey(source), "边 {} 的源节点 {} 不存在", edge.getLabel(), source); + Assert.isTrue(nodeMap.containsKey(target), "边 {} 的目标节点 {} 不存在", edge.getLabel(), target); + } + } + + /** + * 检查是否存在环结构 + * + * @param nodes 节点 + * @param edges 边缘 + */ + private void validateCycle(List nodes, List edges) { + Map> adjacencyList = buildAdjacencyList(edges); + for (Node node : nodes) { + if (hasCycle(node.getId(), adjacencyList, new HashSet<>())) { + throw new IllegalArgumentException("图中存在环结构,起始节点: " + node.getData().getLabel()); + } + } + } + + /** + * 检查是否存在孤立节点 + * + * @param nodes 节点 + * @param edges 边缘 + */ + private void validateIsolatedNodes(List nodes, List edges) { + Set connectedNodes = new HashSet<>(); + for (Edge edge : edges) { + connectedNodes.add(edge.getSource()); + connectedNodes.add(edge.getTarget()); + } + for (Node node : nodes) { + Assert.isTrue(connectedNodes.contains(node.getId()), "节点 {} 是孤立节点,未与任何边连接", node.getId()); + } + } + + /** + * 构建邻接表 + * + * @param edges 边缘 + * @return {@link Map }<{@link Long }, {@link List }<{@link Long }>> + */ + private Map> buildAdjacencyList(List edges) { + Map> adjacencyList = new HashMap<>(); + for (Edge edge : edges) { + adjacencyList.computeIfAbsent(edge.getSource(), k -> new ArrayList<>()).add(edge.getTarget()); + } + return adjacencyList; + } + + /** + * 深度优先搜索(DFS)检查环 + * + * @param nodeId 节点id + * @param adjacencyList 邻接表 + * @param visited 是否已经访问过了 + * @return boolean + */ + private boolean hasCycle(Long nodeId, Map> adjacencyList, Set visited) { + if (visited.contains(nodeId)) { + return true; // 发现环 + } + visited.add(nodeId); + for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) { + if (hasCycle(neighbor, adjacencyList, visited)) { + return true; + } + } + visited.remove(nodeId); + return false; + } + + /** + * 检查两个节点间是否有多根连线 + * + * @param edges 边缘 + */ + private void validateMultipleEdgesBetweenNodes(List edges) { + Map edgeCountMap = new HashMap<>(); + for (Edge edge : edges) { + String key = edge.getSource() + "-" + edge.getTarget(); // 使用 source 和 target 作为唯一标识 + edgeCountMap.put(key, edgeCountMap.getOrDefault(key, 0) + 1); + if (edgeCountMap.get(key) > 1) { + throw new IllegalArgumentException("节点 " + edge.getSource() + " 和节点 " + edge.getTarget() + " 之间存在多根连线"); + } + } + } + + + /** + * 检查节点的关系信息 + */ + private void validateNodeRelations(List nodes, List edges) { + // 构建 source 和 target 的映射 + Map> sourceMap = edges.stream().collect(Collectors.groupingBy(Edge::getSource)); + Map> targetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget)); + + for (Node node : nodes) { + NodeValidator validator = getNodeValidator(node); + // 获取当前节点的 source 和 target + List sources = targetMap.getOrDefault(node.getId(), new ArrayList<>()); + List targets = sourceMap.getOrDefault(node.getId(), new ArrayList<>()); + // 检查节点关系 + ValidatorResult result = validator.validateRelation(node, sources, targets); + Assert.isTrue(result.getValid(), result.getMessage()); + } + } + + + /** + * 获取节点验证器 + * + * @param node 节点 + * @return {@link NodeValidator } + */ + private NodeValidator getNodeValidator(Node node) { + if (NodeType.CUSTOM.equals(node.getType())) { + Assert.isTrue(StrUtil.isNotBlank(node.getCustomType()), "自定义节点类型不能为空"); + return NodeValidatorFactory.getCustom(node.getCustomType()); + } + return NodeValidatorFactory.get(node.getType()); + } + +} diff --git a/src/main/java/com/metis/flow/validator/impl/edge/DefaultEdgeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/edge/DefaultEdgeValidator.java similarity index 61% rename from src/main/java/com/metis/flow/validator/impl/edge/DefaultEdgeValidator.java rename to metis-starter/src/main/java/com/metis/validator/impl/edge/DefaultEdgeValidator.java index 3012647..086d98e 100644 --- a/src/main/java/com/metis/flow/validator/impl/edge/DefaultEdgeValidator.java +++ b/metis-starter/src/main/java/com/metis/validator/impl/edge/DefaultEdgeValidator.java @@ -1,9 +1,9 @@ -package com.metis.flow.validator.impl.edge; +package com.metis.validator.impl.edge; -import com.metis.flow.domain.entity.base.Edge; -import com.metis.flow.enums.EdgeType; -import com.metis.flow.validator.EdgeValidator; -import com.metis.flow.validator.ValidatorResult; +import com.metis.domain.entity.base.Edge; +import com.metis.enums.EdgeType; +import com.metis.validator.EdgeValidator; +import com.metis.validator.ValidatorResult; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/DocumentExtractorNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/DocumentExtractorNodeValidator.java new file mode 100644 index 0000000..7fd2ca8 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/DocumentExtractorNodeValidator.java @@ -0,0 +1,43 @@ +package com.metis.validator.impl.node; + +import cn.hutool.core.lang.Assert; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.DocumentExtractorNodeConfig; +import com.metis.enums.NodeType; +import com.metis.validator.NodeValidator; +import com.metis.validator.ValidatorCodeService; +import com.metis.validator.ValidatorResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DocumentExtractorNodeValidator implements NodeValidator { + private final ValidatorCodeService validatorCodeService; + + @Override + public ValidatorResult validateValue(Node node) { + DocumentExtractorNodeConfig config = node.getConfig(); + validatorCodeService.validateThrow(config); + // 业务检查未通过 + return ValidatorResult.invalid("业务报错"); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + // 1. 检查 targets 数量是否等于 1(只允许一个出) + Assert.isTrue(targets.size() == 1, "节点 {} 的出连接数必须为 1,当前数量: {}", node.getId(), targets.size()); + + return ValidatorResult.valid(); + } + + @Override + public NodeType getType() { + return NodeType.DOCUMENT_EXTRACTOR; + } +} diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/EndNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/EndNodeValidator.java new file mode 100644 index 0000000..9e62e56 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/EndNodeValidator.java @@ -0,0 +1,44 @@ +package com.metis.validator.impl.node; + +import cn.hutool.core.lang.Assert; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.EndNodeConfig; +import com.metis.enums.NodeType; +import com.metis.validator.NodeValidator; +import com.metis.validator.ValidatorResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class EndNodeValidator implements NodeValidator { + + + + @Override + public ValidatorResult validateValue(Node node) { + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + // 1. 结束节点不允许有目标连接 + Assert.isTrue(targets.isEmpty(), "结束节点 {} 不允许有目标连接", node.getId()); + + // 2. 检查 sources 数量是否小于 handles 数量 +// int handleCount = node.getData().getHandles().size(); +// Assert.isTrue(sources.size() <= handleCount, "结束节点 {} 的源连接数超过 handles 数量", node.getId()); + + return ValidatorResult.valid(); + } + + @Override + public NodeType getType() { + return NodeType.END; + } +} diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/LLMNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/LLMNodeValidator.java new file mode 100644 index 0000000..2dd04a6 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/LLMNodeValidator.java @@ -0,0 +1,33 @@ +package com.metis.validator.impl.node; + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.LLMNodeConfig; +import com.metis.enums.NodeType; +import com.metis.validator.NodeValidator; +import com.metis.validator.ValidatorResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LLMNodeValidator implements NodeValidator { + @Override + public ValidatorResult validateValue(Node node) { + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + return ValidatorResult.valid(); + } + + @Override + public NodeType getType() { + return NodeType.LLM; + } +} diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/QuestionClassifierValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/QuestionClassifierValidator.java new file mode 100644 index 0000000..23c3e19 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/QuestionClassifierValidator.java @@ -0,0 +1,31 @@ +package com.metis.validator.impl.node; + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.QuestionClassifierConfig; +import com.metis.enums.NodeType; +import com.metis.validator.NodeValidator; +import com.metis.validator.ValidatorResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class QuestionClassifierValidator implements NodeValidator { + @Override + public ValidatorResult validateValue(Node node) { + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + return ValidatorResult.valid(); + } + + @Override + public NodeType getType() { + return NodeType.QUESTION_CLASSIFIER; + } +} diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/StartNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/StartNodeValidator.java new file mode 100644 index 0000000..797873d --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/StartNodeValidator.java @@ -0,0 +1,98 @@ +package com.metis.validator.impl.node; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.base.NodeVariable; +import com.metis.domain.entity.config.node.StartNodeConfig; +import com.metis.enums.NodeType; +import com.metis.validator.NodeValidator; +import com.metis.validator.ValidatorCodeService; +import com.metis.validator.ValidatorResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class StartNodeValidator implements NodeValidator { + + private final ValidatorCodeService validatorCodeService; + + + @Override + public ValidatorResult validateValue(Node node) { + StartNodeConfig config = node.getConfig(); + validatorCodeService.validateThrow(config); + + List variables = config.getVariables(); + if (CollUtil.isEmpty(variables)) { + return ValidatorResult.valid(); + } + + for (NodeVariable variable : variables) { + switch (variable.getVariableType()) { + // 文本类型校验 + case TEXT_INPUT, PARAGRAPH -> textVariableValidator(variable); + // 下拉框变量校验 + case SELECT -> selectVariableValidator(variable); + // 文件变量校验 + case FILE, FILE_LIST -> fileVariableValidator(variable); + } + } + return ValidatorResult.valid(); + } + + /** + * 文本变量验证器 + * + * @param variable 变量 + */ + private void textVariableValidator(NodeVariable variable) { + Assert.isTrue(ObjectUtil.isNotNull(variable.getMaxLength()), "文本变量 {} {} 的最大长度不能为空", variable.getLabel(), variable.getLabel(), variable.getVariable()); + } + + /** + * 选择变量验证器 + * + * @param variable 变量 + */ + private void selectVariableValidator(NodeVariable variable) { + Assert.isTrue(CollUtil.isNotEmpty(variable.getOptions()), "选择变量 {} {} 的选项不能为空", variable.getLabel(), variable.getLabel(), variable.getVariable()); + } + + /** + * 文件变量验证器 + * + * @param variable 变量 + */ + private void fileVariableValidator(NodeVariable variable) { + Assert.isTrue(ObjectUtil.isNotNull(variable.getMaxLength()), "文本变量 {} {} 的最大长度不能为空", variable.getLabel(), variable.getVariable()); + Assert.isTrue(CollUtil.isNotEmpty(variable.getAllowedFileUploadMethods()), "文本变量 {} {} 的允许上传方式不能为空", variable.getLabel(), variable.getVariable()); + Assert.isTrue(CollUtil.isNotEmpty(variable.getAllowedFileTypes()) || CollUtil.isNotEmpty(variable.getAllowedFileExtensions()), "文本变量 {} 的允许上传文件类型不能为空", variable.getLabel(), variable.getVariable()); + } + + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + // 1. 开始节点不允许有源连接 + Assert.isTrue(sources.isEmpty(), "开始节点 {} 不允许有源连接", node.getId()); + + // 2. 检查 targets 数量是否小于 handles 数量 +// int handleCount = node.getData().getHandles().size(); +// Assert.isTrue(targets.size() <= handleCount, "开始节点 {} 的目标连接数超过 handles 数量", node.getId()); + + return ValidatorResult.valid(); + } + + @Override + public NodeType getType() { + return NodeType.START; + } + +} diff --git a/metis-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/metis-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..0e5852e --- /dev/null +++ b/metis-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.metis.config.MetisStarterAutoConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml index dbb661b..299c6b8 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,11 @@ com.metis metis 1.0.0-SNAPSHOT + pom + + metis-starter + metis-applicant + org.springframework.boot @@ -19,8 +24,9 @@ 17 UTF-8 3.3.4 - 2.0.45 + 2.0.56 1.18.34 + 1.6.2 1.2.3 1.0.0-beta2 3.5.8 @@ -36,126 +42,60 @@ pom import + + dev.langchain4j + langchain4j-open-ai + ${langchain4j.version} + + + dev.langchain4j + langchain4j-mcp + 1.0.0-beta2 + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + com.mikesamuel + json-sanitizer + ${sanitizer.version} + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + mysql + mysql-connector-java + 8.0.33 + + + cn.hutool + hutool-all + 5.8.24 + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + org.springdoc + springdoc-openapi-starter-webmvc-api + 2.2.0 + - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - dev.langchain4j - langchain4j-open-ai - ${langchain4j.version} - - - dev.langchain4j - langchain4j-mcp - 1.0.0-beta2 - - - com.alibaba.fastjson2 - fastjson2 - ${fastjson.version} - - - org.projectlombok - lombok - - - com.mikesamuel - json-sanitizer - ${sanitizer.version} - - - com.baomidou - mybatis-plus-spring-boot3-starter - ${mybatis-plus.version} - - - mysql - mysql-connector-java - 8.0.33 - - - cn.hutool - hutool-all - 5.8.22 - - - org.mapstruct - mapstruct - 1.6.2 - - - org.projectlombok - lombok-mapstruct-binding - 0.2.0 - - - org.springdoc - springdoc-openapi-starter-webmvc-api - 2.2.0 - - - com.github.xiaoymin - knife4j-openapi3-jakarta-spring-boot-starter - 4.4.0 - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 17 - 17 - UTF-8 - - -parameters - - - - - - org.mapstruct - mapstruct-processor - 1.6.2 - - - org.projectlombok - lombok - 1.18.34 - - - org.projectlombok - lombok-mapstruct-binding - 0.2.0 - - - - - - \ No newline at end of file diff --git a/src/main/java/com/metis/controller/TestController.java b/src/main/java/com/metis/controller/TestController.java deleted file mode 100644 index 53dbd80..0000000 --- a/src/main/java/com/metis/controller/TestController.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.metis.controller; - -import com.metis.result.Result; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/test") -public class TestController { - - - @GetMapping - public Result test() { - return Result.ok("测试成功"); - } - -} diff --git a/src/main/java/com/metis/domain/package-info.java b/src/main/java/com/metis/domain/package-info.java deleted file mode 100644 index 8890ae6..0000000 --- a/src/main/java/com/metis/domain/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package com.metis.domain; \ No newline at end of file diff --git a/src/main/java/com/metis/flow/config/FlowMybatisPlusConfiguration.java b/src/main/java/com/metis/flow/config/FlowMybatisPlusConfiguration.java deleted file mode 100644 index 77d35d6..0000000 --- a/src/main/java/com/metis/flow/config/FlowMybatisPlusConfiguration.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.metis.flow.config; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@MapperScan(basePackages = {"com.metis.flow.mapper"}) -public class FlowMybatisPlusConfiguration { -} diff --git a/src/main/java/com/metis/flow/domain/bo/Graph.java b/src/main/java/com/metis/flow/domain/bo/Graph.java deleted file mode 100644 index 626974c..0000000 --- a/src/main/java/com/metis/flow/domain/bo/Graph.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.metis.flow.domain.bo; - -import com.metis.flow.domain.entity.base.Edge; -import com.metis.flow.domain.entity.base.Node; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Data; - -import java.util.List; - -@Data -public class Graph { - - /** - * 边缘 - */ - @Valid - @NotNull(message = "连线不能为空") - private List edges; - - /** - * 节点 - */ - @Valid - @NotNull(message = "节点不能为空") - @Size(min = 1, message = "节点不能为空") - private List nodes; -} diff --git a/src/main/java/com/metis/flow/domain/context/RunningContext.java b/src/main/java/com/metis/flow/domain/context/RunningContext.java deleted file mode 100644 index 891b196..0000000 --- a/src/main/java/com/metis/flow/domain/context/RunningContext.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.metis.flow.domain.context; - -import com.alibaba.fastjson2.JSONObject; -import lombok.Data; - -@Data -public class RunningContext { - - /** - * 系统数据 - */ - private SysContext sys; - - /** - * 自定义数据 - */ - private JSONObject custom; - -} diff --git a/src/main/java/com/metis/flow/domain/context/SysContext.java b/src/main/java/com/metis/flow/domain/context/SysContext.java deleted file mode 100644 index 59be745..0000000 --- a/src/main/java/com/metis/flow/domain/context/SysContext.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.metis.flow.domain.context; - -import lombok.Data; - -import java.util.List; - -@Data -public class SysContext { - - private List file; - - private Long appId; - - private Long userId; - - private Long workflowId; - - private Long instanceId; -} diff --git a/src/main/java/com/metis/flow/domain/entity/base/Node.java b/src/main/java/com/metis/flow/domain/entity/base/Node.java deleted file mode 100644 index c5b0fd9..0000000 --- a/src/main/java/com/metis/flow/domain/entity/base/Node.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.metis.flow.domain.entity.base; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.metis.flow.enums.NodeType; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.util.Map; -import java.util.stream.Collectors; - -@Data -public class Node { - - /** - * id - */ - @NotBlank(message = "节点id不能为空") - private String id; - - /** - * 类型 - */ - @NotNull(message = "节点类型不能为空") - private NodeType type; - - /** - * 位置 - */ - @Valid - @NotNull(message = "节点位置不能为空") - private Position position; - - /** - * 业务数据 - */ - @Valid - @NotNull(message = "节点业务数据不能为空") - private NodeData data; - - - @JsonIgnore - public Map getHandleMap() { - if (CollUtil.isEmpty(data.getHandles())) { - return Map.of(); - } - return data.getHandles().stream().collect(Collectors.toMap(Handle::getId, handle -> handle)); - } - - @JsonIgnore - public T getConfig() { - if (ObjectUtil.isNull(data.getConfig())) { - return null; - } - return (T) data.getConfig().to(type.getConfigClass()); - } - -} diff --git a/src/main/java/com/metis/flow/engine/AppEngineRunnerService.java b/src/main/java/com/metis/flow/engine/AppEngineRunnerService.java deleted file mode 100644 index 8bf6396..0000000 --- a/src/main/java/com/metis/flow/engine/AppEngineRunnerService.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.metis.flow.engine; - -public interface AppEngineRunnerService { - - -} diff --git a/src/main/java/com/metis/flow/runner/NodeRunner.java b/src/main/java/com/metis/flow/runner/NodeRunner.java deleted file mode 100644 index c9a8420..0000000 --- a/src/main/java/com/metis/flow/runner/NodeRunner.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.metis.flow.runner; - -import com.metis.flow.domain.context.RunningContext; -import com.metis.flow.domain.entity.base.Node; -import com.metis.flow.enums.NodeType; - -public interface NodeRunner { - - - /** - * 运行 - * - * @param context 上下文 - * @param node 节点配置信息 - * @return {@link RunningContext } - */ - RunningContext run(RunningContext context, Node node); - - - /** - * 获取节点类型 - * - * @return {@link NodeType } - */ - NodeType getType(); - -} diff --git a/src/main/java/com/metis/flow/runner/RunnerInitialize.java b/src/main/java/com/metis/flow/runner/RunnerInitialize.java deleted file mode 100644 index b2dafa0..0000000 --- a/src/main/java/com/metis/flow/runner/RunnerInitialize.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.metis.flow.runner; - -import com.metis.flow.runner.factory.RunnerFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Service; - -import java.util.Map; - -@Service -public class RunnerInitialize implements ApplicationContextAware { - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - Map runnerMap = applicationContext.getBeansOfType(NodeRunner.class); - runnerMap.forEach((k, v) -> { - RunnerFactory.register(v); - }); - } - -} diff --git a/src/main/java/com/metis/flow/runner/factory/RunnerFactory.java b/src/main/java/com/metis/flow/runner/factory/RunnerFactory.java deleted file mode 100644 index 3a71a82..0000000 --- a/src/main/java/com/metis/flow/runner/factory/RunnerFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.metis.flow.runner.factory; - -import com.metis.flow.enums.NodeType; -import com.metis.flow.runner.NodeRunner; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class RunnerFactory { - - private static final Map MAP = new ConcurrentHashMap<>(8); - - - /** - * 注册 - * - * @param runner 跑步者 - */ - public static void register(NodeRunner runner) { - MAP.put(runner.getType(), runner); - } - - - /** - * 得到 - * - * @param type 类型 - * @return {@link NodeRunner } - */ - public static NodeRunner get(NodeType type) { - return MAP.get(type); - } - - - -} diff --git a/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java b/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java deleted file mode 100644 index 35191e1..0000000 --- a/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.metis.flow.runner.impl; - - -import com.metis.flow.domain.context.RunningContext; -import com.metis.flow.domain.entity.base.Node; -import com.metis.flow.enums.NodeType; -import com.metis.flow.runner.NodeRunner; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -public class EndNodeRunner implements NodeRunner { - - @Override - public RunningContext run(RunningContext context, Node node) { - return context; - } - - @Override - public NodeType getType() { - return NodeType.END; - } - -} diff --git a/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java b/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java deleted file mode 100644 index f43a570..0000000 --- a/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.metis.flow.runner.impl; - -import com.metis.flow.domain.context.RunningContext; -import com.metis.flow.domain.entity.base.Node; -import com.metis.flow.enums.NodeType; -import com.metis.flow.runner.NodeRunner; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -public class StartNodeRunner implements NodeRunner { - - @Override - public RunningContext run(RunningContext context, Node node) { - return context; - } - - @Override - public NodeType getType() { - return NodeType.START; - } -} diff --git a/src/main/java/com/metis/flow/validator/NodeValidator.java b/src/main/java/com/metis/flow/validator/NodeValidator.java deleted file mode 100644 index ecaa51f..0000000 --- a/src/main/java/com/metis/flow/validator/NodeValidator.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.metis.flow.validator; - -import com.metis.flow.domain.entity.base.Node; -import com.metis.flow.enums.NodeType; - -public interface NodeValidator { - - - /** - * 节点验证 - * - * @param node 节点 - * @return {@link ValidatorResult } - */ - ValidatorResult validate(Node node); - - - /** - * 得到类型 - * - * @return {@link NodeType } - */ - NodeType getType(); - -} diff --git a/src/main/java/com/metis/flow/validator/ValidatorInitialize.java b/src/main/java/com/metis/flow/validator/ValidatorInitialize.java deleted file mode 100644 index 7201eb7..0000000 --- a/src/main/java/com/metis/flow/validator/ValidatorInitialize.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.metis.flow.validator; - - -import com.metis.flow.validator.factory.EdgeValidatorFactory; -import com.metis.flow.validator.factory.NodeValidatorFactory; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Service; - -import java.util.Map; - -@Service -public class ValidatorInitialize implements ApplicationContextAware { - - - @Override - public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { - Map edgeMap = applicationContext.getBeansOfType(EdgeValidator.class); - - edgeMap.forEach((k, v) -> { - EdgeValidatorFactory.register(v); - }); - - - Map nodeMap = applicationContext.getBeansOfType(NodeValidator.class); - nodeMap.forEach((k, v) -> { - NodeValidatorFactory.register(v); - }); - } - - -} diff --git a/src/main/java/com/metis/flow/validator/factory/NodeValidatorFactory.java b/src/main/java/com/metis/flow/validator/factory/NodeValidatorFactory.java deleted file mode 100644 index ac00792..0000000 --- a/src/main/java/com/metis/flow/validator/factory/NodeValidatorFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.metis.flow.validator.factory; - -import com.metis.flow.enums.NodeType; -import com.metis.flow.validator.NodeValidator; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - - -public class NodeValidatorFactory { - - private static final Map MAP = new ConcurrentHashMap<>(8); - - /** - * 注册 - * - * @param validator 验证器 - */ - public static void register(NodeValidator validator) { - MAP.put(validator.getType(), validator); - } - - /** - * 得到 - * - * @param type 类型 - * @return {@link NodeValidator } - */ - public static NodeValidator get(NodeType type) { - return MAP.get(type); - } - - -} diff --git a/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java b/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java deleted file mode 100644 index f11af15..0000000 --- a/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.metis.flow.validator.impl; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjectUtil; -import com.metis.flow.domain.bo.BuildApp; -import com.metis.flow.domain.bo.Graph; -import com.metis.flow.domain.entity.base.Edge; -import com.metis.flow.domain.entity.base.Node; -import com.metis.flow.enums.EdgeType; -import com.metis.flow.enums.NodeType; -import com.metis.flow.validator.EdgeValidator; -import com.metis.flow.validator.NodeValidator; -import com.metis.flow.validator.ValidatorResult; -import com.metis.flow.validator.ValidatorService; -import com.metis.flow.validator.factory.EdgeValidatorFactory; -import com.metis.flow.validator.factory.NodeValidatorFactory; -import jakarta.validation.ConstraintViolation; -import jakarta.validation.Validator; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -@Slf4j -@Service -@RequiredArgsConstructor -public class ValidatorServiceImpl implements ValidatorService { - - private final Validator globalValidator; - - @Override - public void validate(BuildApp graph) { - // validation 编程式校验 - Set> validates = globalValidator.validate(graph); - if (CollUtil.isNotEmpty(validates)) { - List errorMessage = validates.stream() - .map(ConstraintViolation::getMessage).toList(); - throw new RuntimeException(String.join(",", errorMessage)); - } - - Graph model = graph.getGraph(); - // 节点参数校验 - validateNode(model.getNodes()); - // 线参数校验 - validateEdge(model.getEdges()); - // 关系验证 - validateRelation(model.getNodes(), model.getEdges()); - } - - - /** - * 验证节点 - * - * @param nodes 节点 - */ - private void validateNode(List nodes) { - List errorMessage = new ArrayList<>(); - for (Node node : nodes) { - NodeType type = node.getType(); - NodeValidator validator = NodeValidatorFactory.get(type); - // 节点校验器 - Assert.isTrue(ObjectUtil.isNotNull(validator), "无:{}类型的节点校验器", type.getName()); - ValidatorResult result = validator.validate(node); - // 返回值检查 - Assert.isTrue(ObjectUtil.isNotNull(result), "类型:{} 的校验器无返回值", validator.getType().getName()); - if (!result.getValid()) { - errorMessage.add(result.getMessage()); - } - } - Assert.isTrue(CollUtil.isEmpty(errorMessage), String.join(",", errorMessage)); - } - - - /** - * 验证边缘 - * - * @param edges - */ - private void validateEdge(List edges) { - List errorMessage = new ArrayList<>(); - for (Edge edge : edges) { - EdgeType type = edge.getType(); - EdgeValidator validator = EdgeValidatorFactory.get(type); - // 节点校验器 - Assert.isTrue(ObjectUtil.isNotNull(validator), "无:{}类型的边校验器", type.getName()); - ValidatorResult result = validator.validate(edge); - // 返回值检查 - Assert.isTrue(ObjectUtil.isNotNull(result), "类型:{} 的校验器无返回值", validator.getType().getName()); - if (!result.getValid()) { - errorMessage.add(result.getMessage()); - } - } - Assert.isTrue(CollUtil.isEmpty(errorMessage), String.join(",", errorMessage)); - } - - - /** - * 验证关系 - * - * @param nodes 节点 - * @param edges 边缘 - */ - private void validateRelation(List nodes, List edges) { - - } - -} diff --git a/src/main/java/com/metis/flow/validator/impl/node/EndNodeValidator.java b/src/main/java/com/metis/flow/validator/impl/node/EndNodeValidator.java deleted file mode 100644 index caebef9..0000000 --- a/src/main/java/com/metis/flow/validator/impl/node/EndNodeValidator.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.metis.flow.validator.impl.node; - -import com.metis.flow.domain.entity.base.Node; -import com.metis.flow.enums.NodeType; -import com.metis.flow.validator.NodeValidator; -import com.metis.flow.validator.ValidatorResult; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -public class EndNodeValidator implements NodeValidator { - - @Override - public ValidatorResult validate(Node node) { - return ValidatorResult.valid(); - } - - @Override - public NodeType getType() { - return NodeType.END; - } -} diff --git a/src/main/java/com/metis/flow/validator/impl/node/StartNodeValidator.java b/src/main/java/com/metis/flow/validator/impl/node/StartNodeValidator.java deleted file mode 100644 index f32d657..0000000 --- a/src/main/java/com/metis/flow/validator/impl/node/StartNodeValidator.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.metis.flow.validator.impl.node; - -import com.metis.flow.domain.entity.base.Node; -import com.metis.flow.enums.NodeType; -import com.metis.flow.validator.NodeValidator; -import com.metis.flow.validator.ValidatorResult; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -public class StartNodeValidator implements NodeValidator { - - @Override - public ValidatorResult validate(Node node) { - return ValidatorResult.valid(); - } - - @Override - public NodeType getType() { - return NodeType.START; - } - -} diff --git a/src/test/java/AnsMsgHandler.java b/src/test/java/AnsMsgHandler.java deleted file mode 100644 index 8d79f62..0000000 --- a/src/test/java/AnsMsgHandler.java +++ /dev/null @@ -1,8 +0,0 @@ - -import java.io.InputStream; - -public interface AnsMsgHandler { - - void actMsg(InputStream is, String line); - -} diff --git a/src/test/java/SSeTest.java b/src/test/java/SSeTest.java deleted file mode 100644 index 63f3d19..0000000 --- a/src/test/java/SSeTest.java +++ /dev/null @@ -1,27 +0,0 @@ -import com.fasterxml.jackson.core.JsonProcessingException; -import com.metis.sseclient.check.SseCheck; -import dev.langchain4j.agent.tool.ToolSpecification; -import dev.langchain4j.mcp.client.DefaultMcpClient; -import dev.langchain4j.mcp.client.transport.McpTransport; -import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport; -import lombok.extern.slf4j.Slf4j; - -import java.util.List; - - -@Slf4j -public class SSeTest { - public static void main(String[] args) throws JsonProcessingException { - McpTransport transport = new HttpMcpTransport.Builder() - .sseUrl("http://localhost:8081/sse") - .build(); - new DefaultMcpClient.Builder() - .transport(transport) - .build() - .listTools(); - - SseCheck sseCheck = new SseCheck("http://localhost:8081/sse"); - List listTools = sseCheck.listTools(); - System.out.println(listTools); - } -} diff --git a/src/test/java/SseClient.java b/src/test/java/SseClient.java deleted file mode 100644 index b3615f6..0000000 --- a/src/test/java/SseClient.java +++ /dev/null @@ -1,77 +0,0 @@ - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; - -/** - * - * @author hyd - * - */ -public class SseClient { - - /** - * 获取SSE输入流。 - * - * @param urlPath - * @return - * @throws IOException - */ - public static InputStream getSseInputStream(String urlPath) throws IOException { - URL url = new URL(urlPath); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - // 这儿根据自己的情况选择get或post - urlConnection.setRequestMethod("GET"); - urlConnection.setDoOutput(true); - urlConnection.setDoInput(true); - urlConnection.setUseCaches(false); - urlConnection.setRequestProperty("Connection", "Keep-Alive"); - urlConnection.setRequestProperty("Charset", "UTF-8"); - //读取过期时间(很重要,建议加上) - urlConnection.setReadTimeout(60 * 1000); - // text/plain模式 - urlConnection.setRequestProperty("Content-Type", "text/plain; charset=UTF-8"); - InputStream inputStream = urlConnection.getInputStream(); - InputStream is = new BufferedInputStream(inputStream); - return is; - } - - /** - * 读取数据。 - * - * @param is - * @param ansMsgHandler - * @throws IOException - */ - public static void readStream(InputStream is, AnsMsgHandler ansMsgHandler) throws IOException { - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - String line = ""; - while ((line = reader.readLine()) != null) { - // 处理数据接口 - ansMsgHandler.actMsg(is, line); - } - // 当服务器端主动关闭的时候,客户端无法获取到信号。现在还不清楚原因。所以无法执行的此处。 - reader.close(); - } catch (IOException e) { - e.printStackTrace(); - throw new IOException("关闭数据流!"); - } - } - - public static void main(String[] args) throws IOException { - String urlPath = "http://localhost:8081/sse"; - InputStream inputStream = getSseInputStream(urlPath); - readStream(inputStream, new AnsMsgHandler() { - - public void actMsg(InputStream is, String line) { - System.out.println(line); - } - }); - } - -}