feat: 流程工具架构开发

This commit is contained in:
2025-04-05 19:46:52 +08:00
parent 716571197f
commit a3a48f8e67
50 changed files with 1819 additions and 70 deletions

View File

@@ -102,6 +102,9 @@
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>

View File

@@ -2,9 +2,8 @@ package com.metis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication
public class MetisApplication {
public static void main(String[] args) {

View File

@@ -0,0 +1,43 @@
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.result.Result;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("/process/definition")
public class ProcessDefinitionController {
private final ProcessDefinitionFacade processDefinitionFacade;
@PostMapping("/create")
public Result<String> create(@RequestBody ProcessBo processBo) {
processDefinitionFacade.create(processBo);
return Result.ok();
}
@PutMapping("/update")
public Result<String> update(@RequestBody ProcessBo processBo) {
processDefinitionFacade.update(processBo);
return Result.ok();
}
@GetMapping("/{deploymentId}")
public Result<App> getByDeploymentId(@PathVariable Long deploymentId) {
App app = processDefinitionFacade.getByDeploymentId(deploymentId);
return Result.ok(app);
}
@DeleteMapping("/{appId}")
public Result<String> delete(@PathVariable Long appId) {
processDefinitionFacade.delete(appId);
return Result.ok();
}
}

View File

@@ -1,45 +0,0 @@
package com.metis.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author Clay
* @date 2022/10/30
*/
@Data
public class BaseEntity implements Serializable {
/**
* 创建者
*/
// private Object createBy;
/**
* 创建时间
*/
@JsonFormat(locale = "zh",timezone = "GMT+8",pattern = "yyyy-MM-dd")
private LocalDateTime createTime;
/**
* 更新者
*/
// private Object updateBy;
/**
* 更新时间
*/
@JsonFormat(locale = "zh",timezone = "GMT+8",pattern = "yyyy-MM-dd")
private LocalDateTime updateTime;
/**
* 逻辑删除字段
*/
private Boolean isDeleted;
}

View File

@@ -0,0 +1,43 @@
package com.metis.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author Clay
* @date 2022/10/30
*/
@Data
public class SimpleBaseEntity implements Serializable {
/**
* 创建时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime updateTime;
/**
* 逻辑删除字段
*/
@TableLogic(value = "0", delval = "1")
@TableField(fill = FieldFill.INSERT)
private Integer isDeleted;
}

View File

@@ -0,0 +1,20 @@
package com.metis.domain.bo;
import com.metis.enums.YesOrNoEnum;
import com.metis.flow.domain.entity.Graph;
import lombok.Data;
@Data
public class ProcessBo {
private Long appId;
private String name;
private String description;
private Graph graph;
private YesOrNoEnum defaultUse;
}

View File

@@ -0,0 +1,52 @@
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.Optional;
/**
* 基础状态枚举
*
* @author ZhangQiang
* @date 2024/10/22
*/
@Getter
@AllArgsConstructor
public enum YesOrNoEnum implements BaseEnum<YesOrNoEnum> {
/**
* 基础状态枚举
*/
YES(1, ""),
NO(0, ""),
;
@JsonValue
private final Integer code;
private final String name;
/**
* 根据 code 转换枚举
*
* @param code 编码
* @return 枚举
*/
public static Optional<YesOrNoEnum> of(Integer code) {
return Optional.ofNullable(BaseEnum.parseByCode(YesOrNoEnum.class, code));
}
/**
* 枚举序列化器(前端传code时自动转换为对应枚举)
*
* @param code 编码
* @return 枚举
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static YesOrNoEnum get(Integer code) {
return BaseEnum.parseByCode(YesOrNoEnum.class, code);
}
}

View File

@@ -0,0 +1,49 @@
package com.metis.facade;
import com.metis.domain.bo.ProcessBo;
import com.metis.flow.domain.entity.App;
import com.metis.flow.domain.entity.CreateApp;
import com.metis.flow.domain.entity.UpdateApp;
import com.metis.flow.engine.AppEngineService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class ProcessDefinitionFacade {
private final AppEngineService appEngineService;
/**
* 创建
*
* @param processBo 过程业务对象
*/
public void create(ProcessBo processBo) {
CreateApp createApp = CreateApp.builder()
.name(processBo.getName())
.graph(processBo.getGraph())
.build();
App app = appEngineService.create(createApp);
}
public App getByDeploymentId(Long deploymentId) {
return appEngineService.getByDeploymentId(deploymentId);
}
public void update(ProcessBo processBo) {
appEngineService.update(UpdateApp.builder()
.defaultUse(processBo.getDefaultUse())
.appId(processBo.getAppId())
.name(processBo.getName())
.graph(processBo.getGraph())
.build());
}
public void delete(Long appId) {
appEngineService.delete(appId);
}
}

View File

@@ -0,0 +1,9 @@
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 {
}

View File

@@ -0,0 +1,6 @@
package com.metis.flow.constant;
public interface BaseConstant {
Integer DEFAULT_VERSION = 0;
}

View File

@@ -0,0 +1,73 @@
package com.metis.flow.convert;
import com.metis.flow.domain.entity.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface BaseAppConvert {
BaseAppConvert INSTANCE = Mappers.getMapper(BaseAppConvert.class);
/**
* 到应用程序
*
* @param buildApp 基础应用
* @return {@link App }
*/
App toApp(BuildApp buildApp);
/**
* 到应用程序
*
* @param baseApp 基础应用
* @return {@link App }
*/
@Mappings({
@Mapping(target = "graph", expression = "java(com.alibaba.fastjson2.JSON.parseObject(baseApp.getGraphJson(), com.metis.flow.domain.entity.Graph.class))"),
@Mapping(target = "deploymentId", source = "id")
})
App toApp(BaseApp baseApp);
/**
* 到基础应用程序
*
* @param buildApp 构建应用程序
* @return {@link BaseApp }
*/
@Mappings({
@Mapping(target = "graphJson", expression = "java(com.alibaba.fastjson2.JSON.toJSONString(buildApp.getGraph()))")
})
BaseApp toBaseApp(BuildApp buildApp);
/**
* 应用
*
* @param baseApps 基础应用
* @return {@link List }<{@link App }>
*/
List<App> toApps(List<BaseApp> baseApps);
/**
* 来构建应用程序
*
* @param createApp 创建应用程序
* @return {@link BuildApp }
*/
BuildApp toBuildApp(CreateApp createApp);
/**
* 来构建应用程序
*
* @param updateApp 更新应用程序
* @return {@link BuildApp }
*/
BuildApp toBuildApp(UpdateApp updateApp);
}

View File

@@ -0,0 +1,16 @@
package com.metis.flow.domain;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class AppQuery {
private Long appId;
private String name;
private String description;
}

View File

@@ -0,0 +1,52 @@
package com.metis.flow.domain.entity;
import com.metis.enums.YesOrNoEnum;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class App {
/**
* 主键
*/
private Long id;
/**
* 部署id
*/
private Long deploymentId;
/**
* 名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 图
*/
private Graph graph;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 版本
*/
private Integer version;
/**
* 默认使用
*/
private YesOrNoEnum defaultUse;
}

View File

@@ -0,0 +1,69 @@
package com.metis.flow.domain.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.metis.domain.SimpleBaseEntity;
import com.metis.enums.YesOrNoEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 基础应用
*
* @author clay
* @date 2025/04/05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName(value = "base_app")
public class BaseApp extends SimpleBaseEntity {
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 应用程序id
*/
@TableField(value = "app_id")
private Long appId;
/**
* app名称
*/
@TableField(value = "name")
private String name;
/**
* 描述
*/
@TableField(value = "description")
private String description;
/**
* 图json数据
*/
@TableField(value = "graph_json")
private String graphJson;
/**
* 版本号
*/
@TableField(value = "version")
private Integer version;
/**
* 创建人id
*/
@TableField(value = "create_user_id")
private Long createUserId;
/**
* 默认使用
*/
private YesOrNoEnum defaultUse;
}

View File

@@ -0,0 +1,33 @@
package com.metis.flow.domain.entity;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public final class BuildApp {
private Long appId;
/**
* 用户id
*/
private Long userId;
/**
* 名字
*/
@NotBlank(message = "流程名称不能为空")
private String name;
@NotNull(message = "流程模型不能为空")
@Valid
private Graph graph;
/**
* 描述
*/
private String description;
}

View File

@@ -0,0 +1,31 @@
package com.metis.flow.domain.entity;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class CreateApp {
/**
* 用户id
*/
private Long userId;
/**
* 名字
*/
@NotBlank(message = "流程名称不能为空")
private String name;
@NotNull(message = "流程模型不能为空")
@Valid
private Graph graph;
/**
* 描述
*/
private String description;
}

View File

@@ -0,0 +1,29 @@
package com.metis.flow.domain.entity;
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<Edge> edges;
/**
* 节点
*/
@Valid
@NotNull(message = "节点不能为空")
@Size(min = 1, message = "节点不能为空")
private List<Node> nodes;
}

View File

@@ -0,0 +1,35 @@
package com.metis.flow.domain.entity;
import com.metis.enums.YesOrNoEnum;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class UpdateApp {
/**
* id
*/
private Long appId;
/**
* 名字
*/
@NotBlank(message = "流程名称不能为空")
private String name;
@NotNull(message = "流程模型不能为空")
@Valid
private Graph graph;
/**
* 描述
*/
private String description;
private YesOrNoEnum defaultUse;
}

View File

@@ -0,0 +1,67 @@
package com.metis.flow.domain.entity.base;
import com.metis.flow.enums.EdgeType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class Edge {
/**
* 唯一标识符
*/
@NotBlank(message = "唯一标识符不能为空")
private String id;
/**
* 标签
*/
private String label;
/**
* 节点类型
*/
@NotNull(message = "线类型不能为空")
private EdgeType type;
/**
* 源节点ID,对应节点id
*/
@NotBlank(message = "源节点ID不能为空")
private String source;
/**
* 目标节点ID,对应节点id
*/
@NotBlank(message = "目标节点ID不能为空")
private String target;
/**
* 源句柄id
*/
@NotBlank(message = "源句柄ID不能为空")
private String sourceHandle;
/**
* 目标句柄id
*/
@NotBlank(message = "目标句柄ID不能为空")
private String targetHandle;
/**
* 边是否动画true/false
*/
private Boolean animated;
/**
* 开始标志
*/
private String markerStart;
/**
* 结束标记
*/
private String markerEnd;
}

View File

@@ -0,0 +1,37 @@
package com.metis.flow.domain.entity.base;
import com.metis.flow.enums.PositionType;
import com.metis.flow.enums.HandleType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 句柄对象
*/
@Data
public class Handle {
/**
* 句柄id
*/
@NotBlank(message = "句柄id不能为空")
private String id;
/**
* 句柄类型
*/
@NotNull(message = "句柄类型不能为空")
private HandleType type;
/**
* 句柄位置
*/
@NotNull(message = "句柄位置不能为空")
private PositionType position;
/**
* 是否可以连接
*/
@NotNull(message = "是否可以连接不能为空")
private Boolean connectable;
}

View File

@@ -0,0 +1,51 @@
package com.metis.flow.domain.entity.base;
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;
@Data
public class Node<T> {
/**
* id
*/
@NotBlank(message = "节点id不能为空")
private String id;
/**
* 类型
*/
@NotNull(message = "节点类型不能为空")
private NodeType type;
/**
* 位置
*/
@Valid
@NotNull(message = "节点位置不能为空")
private Position position;
/**
* 业务数据
*/
@Valid
@NotNull(message = "节点业务数据不能为空")
private NodeData<T> data;
@JsonIgnore
public Map<String, Handle> getHandleMap() {
if (data == null || data.getHandles() == null) {
return Map.of();
}
return data.getHandles().stream().collect(java.util.stream.Collectors.toMap(Handle::getId, handle -> handle));
}
}

View File

@@ -0,0 +1,43 @@
package com.metis.flow.domain.entity.base;
import com.metis.flow.enums.PositionType;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Data
public class NodeData<T> {
/**
* 标签
*/
private String label;
/**
* 图标
*/
private String icon;
/**
* 工具栏位置
*/
private PositionType toolbarPosition;
/**
* 配置
*/
private T config;
/**
* 句柄列表
*/
@Valid
@NotNull(message = "句柄列表不能为空")
@Size(min = 1, message = "句柄列表不能为空")
private List<Handle> handles;
}

View File

@@ -0,0 +1,20 @@
package com.metis.flow.domain.entity.base;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class Position {
/**
* x坐标
*/
@NotNull(message = "x坐标不能为空")
private Double x;
/**
* y坐标
*/
@NotNull(message = "y坐标不能为空")
private Double y;
}

View File

@@ -0,0 +1 @@
package com.metis.flow.domain;

View File

@@ -0,0 +1,79 @@
package com.metis.flow.engine;
import com.metis.flow.domain.AppQuery;
import com.metis.flow.domain.entity.App;
import com.metis.flow.domain.entity.CreateApp;
import com.metis.flow.domain.entity.UpdateApp;
import java.util.List;
public interface AppEngineService {
/**
* 列表页面
*
* @param query 查询
* @return {@link List }<{@link App }>
*/
List<App> listPage(AppQuery query);
/**
* 列表
*
* @param query 查询
* @return {@link List }<{@link App }>
*/
List<App> list(AppQuery query);
/**
* 按身份证领取
*
* @param appId id
* @return {@link App }
*/
App getByAppId(Long appId, Integer version);
/**
* 通过部署id获取
*
* @param deploymentId 部署id
* @return {@link App }
*/
App getByDeploymentId(Long deploymentId);
/**
* 按id列表
*
* @param appIds id
* @return {@link List }<{@link App }>
*/
List<App> listByAppIds(List<Long> appIds);
/**
* 创建
*
* @param createApp 应用程序
* @return {@link App }
*/
App create(CreateApp createApp);
/**
* 更新
*
* @param updateApp 应用程序
* @return {@link App }
*/
App update(UpdateApp updateApp);
/**
* 删除
*
* @param appId id
*/
void delete(Long appId);
}

View File

@@ -0,0 +1,148 @@
package com.metis.flow.engine.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.metis.enums.YesOrNoEnum;
import com.metis.flow.constant.BaseConstant;
import com.metis.flow.convert.BaseAppConvert;
import com.metis.flow.domain.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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class AppEngineServiceImpl implements AppEngineService {
private final BaseAppService baseAppService;
private final ValidatorService validatorService;
@Override
public List<App> listPage(AppQuery query) {
List<BaseApp> list = baseAppService.listPage(query);
return BaseAppConvert.INSTANCE.toApps(list);
}
@Override
public List<App> list(AppQuery query) {
List<BaseApp> list = baseAppService.listQuery(query);
return BaseAppConvert.INSTANCE.toApps(list);
}
@Override
public App getByAppId(Long appId, Integer version) {
BaseApp baseApp = baseAppService.getByAppIdAndVersion(appId, version);
return BaseAppConvert.INSTANCE.toApp(baseApp);
}
@Override
public App getByDeploymentId(Long deploymentId) {
BaseApp baseApp = baseAppService.getById(deploymentId);
return BaseAppConvert.INSTANCE.toApp(baseApp);
}
@Override
public List<App> listByAppIds(List<Long> appIds) {
List<BaseApp> baseApps = baseAppService.listByAppIds(appIds);
return BaseAppConvert.INSTANCE.toApps(baseApps);
}
@Override
public App create(CreateApp createApp) {
BuildApp buildApp = BaseAppConvert.INSTANCE.toBuildApp(createApp);
// 校验
validatorService.validate(buildApp);
// 构建保存对象
BaseApp baseApp = creatBaseApp(buildApp);
// 保存
boolean save = baseAppService.save(baseApp);
Assert.isTrue(save, "保存失败");
return creatApp(baseApp, buildApp);
}
@Override
@Transactional(rollbackFor = Exception.class)
public synchronized App update(UpdateApp updateApp) {
BaseApp entity = baseAppService.getByAppId(updateApp.getAppId());
Assert.isTrue(ObjectUtil.isNotNull(entity), "app不存在");
BuildApp buildApp = BaseAppConvert.INSTANCE.toBuildApp(updateApp);
// 校验
validatorService.validate(buildApp);
// 构建保存对象
BaseApp baseApp = updateBaseApp(buildApp);
if (ObjectUtil.isNotNull(updateApp.getDefaultUse()) && YesOrNoEnum.YES.equals(updateApp.getDefaultUse())) {
baseApp.setDefaultUse(YesOrNoEnum.YES);
baseAppService.updateDefaultUseByAppId(updateApp.getAppId(), YesOrNoEnum.NO);
} else {
baseApp.setDefaultUse(YesOrNoEnum.NO);
}
baseApp.setVersion(entity.getVersion() + 1);
// 保存
boolean save = baseAppService.save(baseApp);
Assert.isTrue(save, "更新失败");
return creatApp(baseApp, buildApp);
}
@Override
public void delete(Long appId) {
baseAppService.removeByAppId(appId);
}
/**
* 创建应用程序
*
* @param baseApp 基础应用
* @param buildApp 构建应用程序
* @return {@link App }
*/
private App creatApp(BaseApp baseApp, BuildApp buildApp) {
App app = BaseAppConvert.INSTANCE.toApp(buildApp);
app.setId(baseApp.getId());
app.setDeploymentId(baseApp.getId());
app.setDefaultUse(baseApp.getDefaultUse());
return app;
}
/**
* 创建应用程序
*
* @param buildApp 应用程序
* @return {@link BaseApp }
*/
private BaseApp creatBaseApp(BuildApp buildApp) {
BaseApp baseApp = BaseAppConvert.INSTANCE.toBaseApp(buildApp);
long id = IdUtil.getSnowflakeNextId();
baseApp.setAppId(id);
baseApp.setId(id);
baseApp.setVersion(BaseConstant.DEFAULT_VERSION);
baseApp.setDefaultUse(YesOrNoEnum.YES);
return baseApp;
}
/**
* 更新基础应用程序
*
* @param buildApp 构建应用程序
* @return {@link BaseApp }
*/
private BaseApp updateBaseApp(BuildApp buildApp) {
BaseApp baseApp = BaseAppConvert.INSTANCE.toBaseApp(buildApp);
long id = IdUtil.getSnowflakeNextId();
baseApp.setId(id);
return baseApp;
}
}

View File

@@ -0,0 +1,41 @@
package com.metis.flow.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum EdgeType {
DEFAULT(1, "default", "默认线"),
;
private final Integer code;
@JsonValue
private final String value;
private final String name;
/**
* 枚举序列化器(前端传code时自动转换为对应枚举)
*
* @param value 值
* @return 枚举
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static EdgeType get(String value) {
return Arrays.stream(EdgeType.class.getEnumConstants())
.filter(e -> e.getValue().equals(value))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,41 @@
package com.metis.flow.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum HandleType {
SOURCE(1, "source", "源节点"),
TARGET(2, "target", "目标节点"),
;
private final Integer code;
@JsonValue
private final String value;
private final String name;
/**
* 枚举序列化器(前端传code时自动转换为对应枚举)
*
* @param value 值
* @return 枚举
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static HandleType get(String value) {
return Arrays.stream(HandleType.class.getEnumConstants())
.filter(e -> e.getValue().equals(value))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,43 @@
package com.metis.flow.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum NodeType {
START(1, "start", "开始"),
END(2, "end", "结束"),
;
private final Integer code;
@JsonValue
private final String value;
private final String name;
/**
* 枚举序列化器(前端传code时自动转换为对应枚举)
*
* @param value 值
* @return 枚举
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static NodeType get(String value) {
return Arrays.stream(NodeType.class.getEnumConstants())
.filter(e -> e.getValue().equals(value))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,49 @@
package com.metis.flow.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/05
*/
@Getter
@AllArgsConstructor
public enum PositionType {
LEFT(1, "left", ""),
RIGHT(2, "right", ""),
TOP(3, "top", ""),
BOTTOM(4, "bottom", "");
private final Integer code;
@JsonValue
private final String value;
private final String name;
/**
* 枚举序列化器(前端传code时自动转换为对应枚举)
*
* @param value 值
* @return 枚举
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static PositionType get(String value) {
return Arrays.stream(PositionType.class.getEnumConstants())
.filter(e -> e.getValue().equals(value))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,20 @@
package com.metis.flow.mapper;
import com.metis.flow.domain.entity.BaseApp;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 基础应用映射器
*
* @author 18209
* @date 2025/04/05
*/
@Mapper
public interface BaseAppMapper extends BaseMapper<BaseApp> {
}

View File

@@ -0,0 +1 @@
package com.metis.flow;

View File

@@ -0,0 +1,77 @@
package com.metis.flow.service;
import com.metis.enums.YesOrNoEnum;
import com.metis.flow.domain.AppQuery;
import com.metis.flow.domain.entity.BaseApp;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 基础应用服务
*
* @author 18209
* @date 2025/04/05
*/
public interface BaseAppService extends IService<BaseApp> {
/**
* 通过应用id获取
*
* @param appId 应用程序id
* @return {@link BaseApp }
*/
BaseApp getByAppId(Long appId);
/**
* 通过id和版本获取
*
* @param appId id
* @param version 版本
* @return {@link BaseApp }
*/
BaseApp getByAppIdAndVersion(Long appId, Integer version);
/**
* 列表页面
*
* @param query 查询
* @return {@link List }<{@link BaseApp }>
*/
List<BaseApp> listPage(AppQuery query);
/**
* 列表查询
*
* @param query 查询
* @return {@link List }<{@link BaseApp }>
*/
List<BaseApp> listQuery(AppQuery query);
/**
* 应用id列表
*
* @param appIds 应用程序id
* @return {@link List }<{@link BaseApp }>
*/
List<BaseApp> listByAppIds(List<Long> appIds);
/**
* 按应用id删除
*
* @param appId 应用程序id
* @return int
*/
int removeByAppId(Long appId);
/**
* 更新默认使用
*
* @param appId 应用程序id
* @param yesOrNoEnum 是或否enum
* @return int
*/
boolean updateDefaultUseByAppId(Long appId, YesOrNoEnum yesOrNoEnum);
}

View File

@@ -0,0 +1,109 @@
package com.metis.flow.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
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.AppQuery;
import com.metis.flow.domain.entity.BaseApp;
import com.metis.flow.mapper.BaseAppMapper;
import com.metis.flow.service.BaseAppService;
import com.metis.utils.PageConditionUtil;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 基础应用服务impl
*
* @author 18209
* @date 2025/04/05
*/
@Service
public class BaseAppServiceImpl extends ServiceImpl<BaseAppMapper, BaseApp>
implements BaseAppService {
@Override
public BaseApp getByAppId(Long appId) {
return this.lambdaQuery()
.eq(BaseApp::getAppId, appId)
.orderByDesc(BaseApp::getVersion)
.last("limit 1")
.one();
}
@Override
public BaseApp getByAppIdAndVersion(Long appId, Integer version) {
return this.lambdaQuery()
.eq(BaseApp::getId, appId)
.eq(BaseApp::getVersion, version)
.last("limit 1")
.one();
}
@Override
public List<BaseApp> listPage(AppQuery query) {
IPage<BaseApp> page = PageConditionUtil.getPage();
LambdaQueryWrapper<BaseApp> wrapper = buildQuery(query);
return baseMapper.selectList(page, wrapper);
}
@Override
public List<BaseApp> listQuery(AppQuery query) {
LambdaQueryWrapper<BaseApp> wrapper = buildQuery(query);
return baseMapper.selectList(wrapper);
}
@Override
public List<BaseApp> listByAppIds(List<Long> appIds) {
return this.lambdaQuery()
.in(BaseApp::getAppId, appIds)
.eq(BaseApp::getDefaultUse, YesOrNoEnum.YES)
.list();
}
@Override
public int removeByAppId(Long appId) {
LambdaQueryWrapper<BaseApp> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(BaseApp::getAppId, appId);
return baseMapper.delete(wrapper);
}
@Override
public boolean updateDefaultUseByAppId(Long appId, YesOrNoEnum yesOrNoEnum) {
return this.lambdaUpdate()
.eq(BaseApp::getAppId, appId)
.set(BaseApp::getDefaultUse, yesOrNoEnum)
.update();
}
/**
* 构建查询
*
* @param query 查询
* @return {@link LambdaQueryWrapper }<{@link BaseApp }>
*/
public LambdaQueryWrapper<BaseApp> buildQuery(AppQuery query) {
LambdaQueryWrapper<BaseApp> wrapper = new LambdaQueryWrapper<>();
wrapper.select(BaseApp::getId, BaseApp::getAppId, BaseApp::getName,
BaseApp::getDescription, BaseApp::getVersion, BaseApp::getIsDeleted,
BaseApp::getCreateTime, BaseApp::getUpdateTime)
.like(StrUtil.isNotBlank(query.getName()), BaseApp::getName, query.getName())
.like(StrUtil.isNotBlank(query.getDescription()), BaseApp::getDescription, query.getDescription());
if (ObjectUtil.isNotNull(query.getAppId())) {
wrapper.eq(BaseApp::getAppId, query.getAppId());
} else {
wrapper.eq(BaseApp::getDefaultUse, YesOrNoEnum.YES);
}
return wrapper;
}
}

View File

@@ -0,0 +1,24 @@
package com.metis.flow.validator;
import com.metis.flow.domain.entity.base.Edge;
import com.metis.flow.enums.EdgeType;
public interface EdgeValidator {
/**
* 线验证
*
* @param edge 线
* @return {@link ValidatorResult }
*/
ValidatorResult validate(Edge edge);
/**
* 得到类型
*
* @return {@link EdgeType }
*/
EdgeType getType();
}

View File

@@ -0,0 +1,25 @@
package com.metis.flow.validator;
import com.metis.flow.domain.entity.base.Node;
import com.metis.flow.enums.NodeType;
public interface NodeValidator<T> {
/**
* 节点验证
*
* @param node 节点
* @return {@link ValidatorResult }
*/
ValidatorResult validate(Node<T> node);
/**
* 得到类型
*
* @return {@link NodeType }
*/
NodeType getType();
}

View File

@@ -0,0 +1,34 @@
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<String, EdgeValidator> edgeMap = applicationContext.getBeansOfType(EdgeValidator.class);
edgeMap.forEach((k, v) -> {
EdgeValidatorFactory.register(v);
});
Map<String, NodeValidator> nodeMap = applicationContext.getBeansOfType(NodeValidator.class);
nodeMap.forEach((k, v) -> {
NodeValidatorFactory.register(v);
});
}
}

View File

@@ -0,0 +1,25 @@
package com.metis.flow.validator;
import lombok.Data;
@Data
public final class ValidatorResult {
private final Boolean valid;
private final String message;
private ValidatorResult(Boolean valid, String message) {
this.valid = valid;
this.message = message;
}
public static ValidatorResult valid() {
return new ValidatorResult(true, "");
}
public static ValidatorResult invalid(String message) {
return new ValidatorResult(false, message);
}
}

View File

@@ -0,0 +1,16 @@
package com.metis.flow.validator;
import com.metis.flow.domain.entity.BuildApp;
public interface ValidatorService {
/**
* 验证
*
* @param graph 流程信息
*/
void validate(BuildApp graph);
}

View File

@@ -0,0 +1,42 @@
package com.metis.flow.validator.factory;
import com.metis.flow.enums.EdgeType;
import com.metis.flow.validator.EdgeValidator;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class EdgeValidatorFactory implements ApplicationContextAware {
private static final Map<EdgeType, EdgeValidator> MAP = new ConcurrentHashMap<>(8);
/**
* 注册
*
* @param validator 验证器
*/
public static void register(EdgeValidator validator) {
MAP.put(validator.getType(), validator);
}
/**
* 得到
*
* @param type 类型
* @return {@link EdgeValidator }
*/
public static EdgeValidator get(EdgeType type) {
return MAP.get(type);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, EdgeValidator> validatorMap = applicationContext.getBeansOfType(EdgeValidator.class);
validatorMap.forEach((k, v) -> {
register(v);
});
}
}

View File

@@ -0,0 +1,34 @@
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<NodeType, NodeValidator<Object>> MAP = new ConcurrentHashMap<>(8);
/**
* 注册
*
* @param validator 验证器
*/
public static void register(NodeValidator<Object> validator) {
MAP.put(validator.getType(), validator);
}
/**
* 得到
*
* @param type 类型
* @return {@link NodeValidator }<{@link T }>
*/
public static <T> NodeValidator<T> get(NodeType type) {
return (NodeValidator<T>) MAP.get(type);
}
}

View File

@@ -0,0 +1,111 @@
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.entity.BuildApp;
import com.metis.flow.domain.entity.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<ConstraintViolation<BuildApp>> validates = globalValidator.validate(graph);
if (CollUtil.isNotEmpty(validates)) {
List<String> 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<Node> nodes) {
List<String> errorMessage = new ArrayList<>();
for (Node node : nodes) {
NodeType type = node.getType();
NodeValidator<Object> 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<Edge> edges) {
List<String> 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<Node> nodes, List<Edge> edges) {
}
}

View File

@@ -0,0 +1,22 @@
package com.metis.flow.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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class DefaultEdgeValidator implements EdgeValidator {
@Override
public ValidatorResult validate(Edge edge) {
return ValidatorResult.valid();
}
@Override
public EdgeType getType() {
return EdgeType.DEFAULT;
}
}

View File

@@ -0,0 +1,23 @@
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<Object> {
@Override
public ValidatorResult validate(Node<Object> node) {
return ValidatorResult.valid();
}
@Override
public NodeType getType() {
return NodeType.END;
}
}

View File

@@ -0,0 +1,24 @@
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<Object> {
@Override
public ValidatorResult validate(Node<Object> node) {
return ValidatorResult.valid();
}
@Override
public NodeType getType() {
return NodeType.START;
}
}

View File

@@ -1,9 +1,6 @@
package com.metis.mybatis;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
@@ -37,7 +34,7 @@ public class MybatisPlusConfiguration {
paginationInnerInterceptor.setOverflow(false);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 添加防止全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
@@ -51,13 +48,6 @@ public class MybatisPlusConfiguration {
return new BaseEntityMetaObjectHandler();
}
/**
* 乐观锁插件
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
/**
* 自定义SQL注入器
*
@@ -69,14 +59,6 @@ public class MybatisPlusConfiguration {
}
/**
* 使用网卡信息绑定雪花生成器,实现集群生成id不重复
*/
@Bean
public IdentifierGenerator idGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
}

View File

@@ -35,7 +35,7 @@ public class SseCheck {
private final OkHttpClient client;
private EventSource eventSource;
private final String clientVersion = "1.0";
private final String clientName = "metis";
private final String clientName = "langchain4j";
private final String protocolVersion = "2024-11-05";
@@ -99,9 +99,10 @@ public class SseCheck {
return;
}
final Request finalInitializationNotification = initializationNotification;
execute(httpRequest, operationId)
CompletableFuture<JsonNode> future = execute(httpRequest, operationId)
.thenCompose(originalResponse -> execute(finalInitializationNotification, null)
.thenCompose(nullNode -> CompletableFuture.completedFuture(originalResponse)));
JsonNode jsonNode = future.get();
// log.debug("MCP server capabilities: {}", capabilities.get("result"));
} catch (Exception e) {
throw new RuntimeException(e);
@@ -152,6 +153,13 @@ public class SseCheck {
private CompletableFuture<JsonNode> execute(Request request, Long id) {
CompletableFuture<JsonNode> future = new CompletableFuture<>();
try {
Response execute = client.newCall(request).execute();
System.out.println(execute);
} catch (IOException e) {
throw new RuntimeException(e);
}
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {

View File

@@ -1,10 +1,10 @@
# Spring配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://frp.feashow.cn:39306/metis?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: yyz@2024
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: frp.feashow.cn

View File

@@ -5,3 +5,27 @@ server:
spring:
application:
name: metis
profiles:
active: dev
# MyBatis Plus 配置
mybatis-plus:
global-config:
# 关闭MP3.0自带的banner
banner: false
db-config:
#主键类型 0:"数据库ID自增", 1:"不操作", 2:"用户输入ID",3:"数字型snowflake", 4:"全局唯一ID UUID", 5:"字符串型snowflake";
id-type: 3
#字段策略
insert-strategy: not_null
update-strategy: not_null
select-strategy: not_null
#驼峰下划线转换
table-underline: true
logic-delete-field: is_deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

View File

@@ -1,6 +1,9 @@
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;
@@ -9,6 +12,14 @@ 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<ToolSpecification> listTools = sseCheck.listTools();
System.out.println(listTools);