From ac4626e0c366cb7b9a6cf11999584eb58e6ef603 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Thu, 5 Dec 2024 18:18:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9C=80=E8=A6=81=E9=87=8D=E5=86=99=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E7=9B=B8=E5=85=B3=E7=9A=84=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/docs/api.md | 659 +++++++ backend/docs/architecture.md | 1598 +++++++++++++++++ frontend/api.md | 659 +++++++ frontend/package-lock.json | 621 ++++++- frontend/package.json | 9 + frontend/src/App.tsx | 1 + .../components/FlowDesigner/index.module.css | 32 - .../Edit/components/FlowDesigner/index.tsx | 471 ----- .../Edit/components/FormDesigner/index.tsx | 59 - .../Edit/components/NodeConfig/index.tsx | 131 -- .../pages/Workflow/Definition/Edit/index.tsx | 134 -- .../src/pages/Workflow/Definition/index.tsx | 177 -- .../src/pages/Workflow/Instance/index.tsx | 12 - frontend/src/pages/Workflow/Monitor/index.tsx | 12 - frontend/src/pages/Workflow/service.ts | 104 -- frontend/src/pages/Workflow/types.ts | 161 -- .../flow-designer/components/FlowGraph.tsx | 212 --- .../components/NodeContextMenu.tsx | 20 - frontend/src/pages/flow-designer/index.tsx | 88 - frontend/src/pages/flow-designer/service.ts | 24 - frontend/src/pages/flow-designer/types.ts | 41 - frontend/src/router/index.tsx | 101 +- frontend/src/types/user.ts | 26 - frontend/src/types/x6.d.ts | 1 - frontend/tsconfig.json | 3 +- 25 files changed, 3579 insertions(+), 1777 deletions(-) create mode 100644 backend/docs/api.md create mode 100644 backend/docs/architecture.md create mode 100644 frontend/api.md delete mode 100644 frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.module.css delete mode 100644 frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.tsx delete mode 100644 frontend/src/pages/Workflow/Definition/Edit/components/FormDesigner/index.tsx delete mode 100644 frontend/src/pages/Workflow/Definition/Edit/components/NodeConfig/index.tsx delete mode 100644 frontend/src/pages/Workflow/Definition/Edit/index.tsx delete mode 100644 frontend/src/pages/Workflow/Definition/index.tsx delete mode 100644 frontend/src/pages/Workflow/Instance/index.tsx delete mode 100644 frontend/src/pages/Workflow/Monitor/index.tsx delete mode 100644 frontend/src/pages/Workflow/service.ts delete mode 100644 frontend/src/pages/Workflow/types.ts delete mode 100644 frontend/src/pages/flow-designer/components/FlowGraph.tsx delete mode 100644 frontend/src/pages/flow-designer/components/NodeContextMenu.tsx delete mode 100644 frontend/src/pages/flow-designer/index.tsx delete mode 100644 frontend/src/pages/flow-designer/service.ts delete mode 100644 frontend/src/pages/flow-designer/types.ts delete mode 100644 frontend/src/types/user.ts delete mode 100644 frontend/src/types/x6.d.ts diff --git a/backend/docs/api.md b/backend/docs/api.md new file mode 100644 index 00000000..9e674001 --- /dev/null +++ b/backend/docs/api.md @@ -0,0 +1,659 @@ +# API 接口文档 + +## 1. 通用说明 + +### 1.1 接口规范 + +- 基础路径: `/api/v1` +- 请求方式: REST风格 +- 数据格式: JSON +- 字符编码: UTF-8 +- 时间格式: ISO8601 (YYYY-MM-DDTHH:mm:ss.SSSZ) + +### 1.2 通用响应格式 + +```json +{ + "code": 0, // 响应码,0表示成功,非0表示失败 + "message": "成功", // 响应消息 + "data": { // 响应数据 + // 具体数据结构 + } +} +``` + +### 1.3 通用查询参数 + +分页查询参数: +```json +{ + "page": 1, // 页码,从1开始 + "size": 10, // 每页大小 + "sort": ["id,desc"], // 排序字段,可选 + "keyword": "", // 关键字搜索,可选 +} +``` + +### 1.4 通用错误码 + +| 错误码 | 说明 | 处理建议 | +|--------|------|----------| +| 0 | 成功 | - | +| 1000 | 系统内部错误 | 联系管理员 | +| 1001 | 数据库操作失败 | 重试或联系管理员 | +| 1002 | 并发操作冲突 | 刷新后重试 | +| 2000 | 参数验证失败 | 检查参数 | +| 2001 | 数据不存在 | 检查参数 | +| 2002 | 数据已存在 | 检查参数 | + +## 2. 工作流定义��口 + +### 2.1 创建工作流定义 + +**接口说明**:创建新的工作流定义 + +**请求路径**:POST /workflow-definitions + +**请求参数**: +```json +{ + "code": "string", // 工作流编码,必填,唯一 + "name": "string", // 工作流名称,必填 + "description": "string", // 描述,可选 + "nodeConfig": { // 节点配置,必填 + "nodes": [{ + "id": "string", // 节点ID + "type": "string", // 节点类型 + "name": "string", // 节点名称 + "config": { // 节点配置 + // 具体配置项根据节点类型定义 + } + }] + }, + "transitionConfig": { // 流转配置,必填 + "transitions": [{ + "from": "string", // 来源节点ID + "to": "string", // 目标节点ID + "condition": "string" // 流转条件 + }] + }, + "formDefinition": { // 表单定义,必填 + // 表单配置项 + }, + "graphDefinition": { // 图形定义,必填 + // 图形布局配置 + } +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "id": "long", // 工作流定义ID + "code": "string", // 工作流编码 + "name": "string", // 工作流名称 + "description": "string", // 描述 + "version": 1, // 版本号 + "status": "DRAFT", // 状态:DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用 + "enabled": true, // 是否启用 + "nodeConfig": {}, // 节点配置 + "transitionConfig": {}, // 流转配置 + "formDefinition": {}, // 表单定义 + "graphDefinition": {}, // 图形定义 + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + } +} +``` + +### 2.2 更新工作流定义 + +**接口说明**:更新工作流定义,仅草稿状态可更新 + +**请求路径**:PUT /workflow-definitions/{id} + +**路径参数**: +- id: 工作流定义ID + +**请求参数**:同创建接口 + +**响应数据**:同创建接口 + +### 2.3 发布工作流定义 + +**接口说明**:发布工作流定义,使其可被使用 + +**请求路径**:POST /workflow-definitions/{id}/publish + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.4 禁用工作流定义 + +**接口说明**:禁用工作流定义,禁止新建实例 + +**请求路径**:POST /workflow-definitions/{id}/disable + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.5 启用工作流定义 + +**接口说明**:启用已��用的工作流定义 + +**请求路径**:POST /workflow-definitions/{id}/enable + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.6 创建新版本 + +**接口说明**:基于现有工作流定义创建新版本 + +**请求路径**:POST /workflow-definitions/{id}/versions + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.7 查询工作流定义列表 + +**接口说明**:分页查询工作流定义列表 + +**请求路径**:GET /workflow-definitions + +**请求参数**: +```json +{ + "page": 1, // 页码,从1开始 + "size": 10, // 每页大小 + "sort": ["id,desc"], // 排序字段 + "keyword": "", // 关键字搜索 + "status": "DRAFT", // 状态过滤 + "enabled": true // 是否启用 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "content": [{ // 列表数据 + // 工作流定义数据,同创建接口 + }], + "totalElements": 100, // 总记录数 + "totalPages": 10, // 总页数 + "size": 10, // 每页大小 + "number": 1 // 当前页码 + } +} +``` + +## 3. 工作流实例接口 + +### 3.1 创建工作流实例 + +**接口说明**:创建工作流实例 + +**请求路径**:POST /workflow-instances + +**请求参数**: +```json +{ + "definitionId": "long", // 工作流定义ID,必填 + "businessKey": "string", // 业务标识,可选 + "variables": { // 初始变量,可选 + "key": "value" + } +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "id": "long", // 实例ID + "definitionId": "long", // 定义ID + "businessKey": "string",// 业务标识 + "status": "CREATED", // 状态:CREATED-已创建、RUNNING-运行中、SUSPENDED-已暂停、COMPLETED-已完成、TERMINATED-已终止 + "startTime": "string", // 开始时间 + "endTime": "string", // 结束时间 + "variables": {}, // 变量 + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + } +} +``` + +### 3.2 启动工作流实例 + +**接口说明**:启动工作流实例 + +**请求路径**:POST /workflow-instances/{id}/start + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.3 暂停工作流实例 + +**接口说明**:暂停运行中的工作流实例 + +**请求路径**:POST /workflow-instances/{id}/suspend + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.4 恢复工作流实例 + +**接口说明**:恢复已暂停的工作流实例 + +**请求路径**:POST /workflow-instances/{id}/resume + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.5 终止工作流实例 + +**接口说明**:强制终止工作流实例 + +**请求路径**:POST /workflow-instances/{id}/terminate + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.6 查询工作流实例列表 + +**接口说明**:分页查询工作流实例列表 + +**请求路径**:GET /workflow-instances + +**请求参数**: +```json +{ + "page": 1, // 页码,从1开始 + "size": 10, // 每页大小 + "sort": ["id,desc"], // 排序字段 + "keyword": "", // 关键字搜索 + "status": "RUNNING", // 状态过滤 + "definitionId": "long", // 定义ID过滤 + "businessKey": "string" // 业务标识过滤 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "content": [{ // 列表数据 + // 工作流实例数据,同创建接口 + }], + "totalElements": 100, // 总记录数 + "totalPages": 10, // 总页数 + "size": 10, // 每页大小 + "number": 1 // 当前页码 + } +} +``` + +## 4. 节点实例接口 + +### 4.1 查询节点实例列表 + +**接口说明**:查询工作流实例的节点列表 + +**请求路径**:GET /workflow-instances/{instanceId}/nodes + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "status": "RUNNING" // 状态过滤,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "id": "long", // 节点实例ID + "workflowInstanceId": "long", // 工作流实例ID + "nodeId": "string", // 节点定义ID + "nodeName": "string", // 节点名称 + "nodeType": "string", // 节点类型 + "status": "string", // 状态:CREATED、RUNNING、COMPLETED、FAILED + "startTime": "string", // 开始时间 + "endTime": "string", // 结束时间 + "output": "string", // 输出结果 + "error": "string", // 错误信息 + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + }] +} +``` + +## 5. 工作流变量接口 + +### 5.1 设置变量 + +**接口说明**:设置工作流实例变量 + +**请求路径**:POST /workflow-instances/{instanceId}/variables + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "name": "string", // 变量名,必填 + "value": "object", // 变量值,必�� + "scope": "GLOBAL" // 作用域:GLOBAL-全局、NODE-节点,可选,默认GLOBAL +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功" +} +``` + +### 5.2 获取变量 + +**接口说明**:获取工作流实例变量 + +**请求路径**:GET /workflow-instances/{instanceId}/variables + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "scope": "GLOBAL" // 作用域过滤,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "variableName": "variableValue" + } +} +``` + +## 6. 工作流日志接口 + +### 6.1 查询日志列表 + +**接口说明**:查询工作流实例日志 + +**请求路径**:GET /workflow-instances/{instanceId}/logs + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "nodeId": "string", // 节点ID过滤,可选 + "level": "INFO", // 日志级别过滤:DEBUG、INFO、WARN、ERROR,可选 + "startTime": "string", // 开始时间过滤,可选 + "endTime": "string" // 结束时间过滤,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "id": "long", // 日志ID + "workflowInstanceId": "long", // 工作流实例ID + "nodeId": "string", // 节点ID + "level": "string", // 日志级别 + "content": "string", // 日志内容 + "detail": "string", // 详细信息 + "createTime": "string" // 创建时间 + }] +} +``` + +## 7. 节点类型接口 + +### 7.1 查询节点类型列表 + +**接口说明**:查询可用的节点类型列表 + +**请求路径**:GET /node-types + +**请求参数**: +```json +{ + "enabled": true, // 是否启用过滤,可选 + "category": "TASK" // 类型分类过滤:TASK、EVENT、GATEWAY,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "id": "long", // 节点类型ID + "code": "string", // 节点类型编码 + "name": "string", // 节点类型名称 + "category": "string", // 分类 + "description": "string", // 描述 + "enabled": true, // 是否启用 + "icon": "string", // 图标 + "color": "string", // 颜色 + "executors": [{ // 执行器列表(仅TASK类型) + "code": "string", // 执行器编码 + "name": "string", // 执行器名称 + "description": "string", // 描述 + "configSchema": {} // 配置模式 + }], + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + }] +} +``` + +### 7.2 获取节点执行器列表 + +**接口说明**:获取指定节点类型支持的执行器列表 + +**请求路径**:GET /node-types/{code}/executors + +**路径参数**: +- code: 节点类型编码 + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "code": "string", // 执行器编码 + "name": "string", // 执行器名称 + "description": "string", // 描述 + "configSchema": { // 配置模式 + "type": "object", + "properties": { + // 具体配置项定义 + }, + "required": [] // 必填项 + } + }] +} +``` + +## 8. 错误码说明 + +### 8.1 系统错误 (1xxx) +- 1000: 系统内部错误 +- 1001: 数据库操作失败 +- 1002: 并发操作冲突 + +### 8.2 通用业务错误 (2xxx) +- 2000: 参数验证失败 +- 2001: 数据不存在 +- 2002: 数据已存在 + +### 8.3 工作流定义错误 (3xxx) +- 3000: 工作流定义不存在 +- 3001: 工作流编码已存在 +- 3002: 工作流定义非草稿状态 +- 3003: 工作流定义未发布 +- 3004: 工作流定义已禁用 +- 3005: 节点配置无效 +- 3006: 流转配置无效 +- 3007: 表单配置无效 + +### 8.4 工作流实例错误 (4xxx) +- 4000: 工作流实例不存在 +- 4001: 工作流实例状态无效 +- 4002: 工作流实例已终止 +- 4003: 节点实例不存在 +- 4004: 变量类型无效 + +## 9. 数据结构说明 + +### 9.1 工作流状态 + +```typescript +enum WorkflowStatus { + DRAFT = "DRAFT", // 草稿 + PUBLISHED = "PUBLISHED", // 已发布 + DISABLED = "DISABLED" // 已禁用 +} +``` + +### 9.2 实例状态 + +```typescript +enum InstanceStatus { + CREATED = "CREATED", // 已创建 + RUNNING = "RUNNING", // 运行中 + SUSPENDED = "SUSPENDED", // 已暂停 + COMPLETED = "COMPLETED", // 已完成 + TERMINATED = "TERMINATED"// 已终止 +} +``` + +### 9.3 节点状态 + +```typescript +enum NodeStatus { + CREATED = "CREATED", // 已创建 + RUNNING = "RUNNING", // 运行中 + COMPLETED = "COMPLETED", // 已完成 + FAILED = "FAILED" // 失败 +} +``` + +### 9.4 日志级别 + +```typescript +enum LogLevel { + DEBUG = "DEBUG", + INFO = "INFO", + WARN = "WARN", + ERROR = "ERROR" +} +``` + +### 9.5 变量作用域 + +```typescript +enum VariableScope { + GLOBAL = "GLOBAL", // 全局变量 + NODE = "NODE" // 节点变量 +} +``` + +## 10. 最佳实践 + +### 10.1 工作流设计 + +1. 工作流定义创建流程: + - 创建草稿 + - 配置节点和流转 + - 验证配置 + - 发布使用 + +2. 节点类型选择: + - 根据业务场景选择合适的节点类型 + - 配置合适的执行器 + - 设置必要的变量 + +### 10.2 实例管理 + +1. 实例生命周期管理: + - 创建 -> 启动 -> 运行 -> 完成 + - 必要时可暂停或终止 + - 记录关键节点日志 + +2. 变量使用: + - 合理使用变量作用域 + - 及时清理无用变量 + - 注意变量类型匹配 + +### 10.3 错误处理 + +1. 异常处理: + - 捕获所有可能的错误码 + - 提供友好的错误提示 + - 记录详细的错误日志 + +2. 重试机制: + - 对非致命错误进行重试 + - 设置合理的重试间隔 + - 限制最大重试次数 + +### 10.4 性能优化 + +1. 查询优化: + - 合理使用分页 + - 避免大量数据查询 + - 使用合适的查询条件 + +2. 缓存使用: + - 缓存常用数据 + - 及时更新缓存 + - 避免缓存穿透 \ No newline at end of file diff --git a/backend/docs/architecture.md b/backend/docs/architecture.md new file mode 100644 index 00000000..b2a7c1fd --- /dev/null +++ b/backend/docs/architecture.md @@ -0,0 +1,1598 @@ +# 工作流引擎架构文档 + +## 1. 系统架构 + +### 1.1 整体架构 +``` +com.qqchen.deploy.backend.workflow +├── engine/ # 工作流引擎核心 +│ ├── context/ # 工作流上下文 +│ ├── executor/ # 节点执行器 +│ ├── parser/ # 工作流定义解析器 +│ ├── transition/ # 流转控制 +│ └── node/ # 节点实现 +├── entity/ # 实体类 +├── repository/ # 数据访问层 +├── service/ # 业务逻辑层 +├── dto/ # 数据传输对象 +├── converter/ # 对象转换器 +├── query/ # 查询条件 +└── enums/ # 枚举定义 +``` + +### 1.2 核心模块说明 + +#### 1.2.1 工作流引擎核心 (engine) +- **WorkflowEngine**: 工作流引擎接口,定义工作流生命周期管理方法 +- **DefaultWorkflowEngine**: 默认实现,负责工作流实例的创建、执行、暂停、恢复等 +- **WorkflowContext**: 工作流上下文,管理工作流执行过程中的变量和状态 +- **NodeExecutor**: 节点执行器接口,不同类型节点的执行实现 +- **TransitionExecutor**: 流转执行器,负责节点间的流转逻辑 + +#### 1.2.2 实体模型 (entity) +- **WorkflowDefinition**: 工作流定义,包含节点和流转配置 +- **WorkflowInstance**: 工作流实例,工作流定义的运行时实例 +- **NodeInstance**: 节点实例,工作流中具体节点的运行时实例 +- **NodeConfig**: 节点配置,定义节点的属性和行为 +- **TransitionConfig**: 流转配置,定义节点间的连接和条件 +- **WorkflowVariable**: 工作流变量,存储工作流执行过程中的数据 +- **WorkflowLog**: 工作流日志,记录工作流执行过程 + +#### 1.2.3 数据访问层 (repository) +- **IWorkflowDefinitionRepository**: 工作流定义仓库 +- **IWorkflowInstanceRepository**: 工作流实例仓库 +- **INodeInstanceRepository**: 节点实例仓库 +- **IWorkflowVariableRepository**: 工作流变量仓库 +- **IWorkflowLogRepository**: 工作流日志仓库 + +#### 1.2.4 业务逻辑层 (service) +- **IWorkflowDefinitionService**: 工作流定义服务 +- **IWorkflowInstanceService**: 工作流实例服务 +- **IWorkflowVariableService**: 工作流变量服务 +- **IWorkflowLogService**: 工作流日志服务 + +## 2. 已实现功能 + +### 2.1 工作流定义 +- [x] 工作流基本信息管理(创建、修改、删除) +- [x] 工作流状态管理(草稿、发布、禁 +- [x] 节点配置管理 +- [x] 流转配置管理 +- [x] 配置验证 + +### 2.2 工作流执行 +- [x] 工作流实例创建 +- [x] 节点执行框架 +- [x] 基本流转控制 +- [x] 变量管理 +- [x] 日志记录 + +### 2.3 节点类型 +- [x] 开始节点 +- [x] 结束节点 +- [x] 任务节点 + - [x] Shell执行器 + - [x] HTTP执行器 + - [x] Java执行器 +- [x] 网关节点 + - [x] 排他网关 + - [x] 并行网关 + - [x] 包容网关 + +### 2.4 基础设施 +- [x] 数据库表结构 +- [x] 基础异常处理 +- [x] 基本日志记录 +- [x] DTO/Entity转换 + +## 3. 待改进功能 + +### 3.1 核心功能完善 + +#### 3.1.1 工作流定义 +- [ ] 工作流定义版本控制 +- [ ] 工作流模板管理 +- [ ] 工作流导入导出 +- [ ] 工作流克隆 +- [ ] 表单配置管理 + +#### 3.1.2 工作流执行 +- [ ] 工作流实例暂停/恢复 +- [ ] 子流程支持 +- [ ] 定时调度 +- [ ] 条件表达式引擎优化 +- [ ] 变量作用域隔离 +- [ ] 变量类型自动转换 +- [ ] 变量生命周期管理 + +#### 3.1.3 节点功能 +- [ ] 节点执行重试机制 +- [ ] 节点超时控制 +- [ ] 节点执行资源限制 +- [ ] Shell执行安全控制 +- [ ] HTTP请求重试配置 +- [ ] 自定义节点扩展机制 + +### 3.2 性能优化 + +#### 3.2.1 数据库优化 +- [ ] 添加必要索引 + - [ ] NodeConfig表工作定ID索引 + - [ ] NodeConfig表节点类型索引 + - [ ] TransitionConfig表源节点和目标节点索引 +- [ ] 大表分表策略 + - [ ] 工作流实例表 + - [ ] 节点实例表 + - [ ] 工作流日志表 +- [ ] 数据库连接池优化 + +#### 3.2.2 并发控制 +- [ ] 工作流实例锁机制 +- [ ] 节点实例锁机制 +- [ ] 分布式锁实现 +- [ ] 并发限制 + +#### 3.2.3 缓存优化 +- [ ] 工作流定义缓存 +- [ ] 节点配置缓存 +- [ ] 变量缓存 + +### 3.3 可用性提升 + +#### 3.3.1 高可用 +- [ ] 工作流热备份 +- [ ] 故障自动恢复 +- [ ] 限流熔断 +- [ ] 任务队列 + +#### 3.3.2 监控运维 +- [ ] 工作流监控指标 +- [ ] 性能统计 +- [ ] 日志归档 +- [ ] 数据清理策略 + +### 3.4 安全性增强 + +#### 3.4.1 访问控制 +- [ ] 工作流权限管理 +- [ ] 节点权限控制 +- [ ] 变量访问控制 +- [ ] 操作审计日志 + +#### 3.4.2 数据安全 +- [ ] 敏感数据加密 +- [ ] 节点执行隔离 +- [ ] 资源使用限制 +- [ ] 安全规则配置 + +## 4. 技术栈 + +### 4.1 基础框架 +- Spring Boot +- Spring Data JPA +- Hibernate +- MapStruct + +### 4.2 数据库 +- MySQL +- Flyway (数据库版本管理) + +### 4.3 工具库 +- Jackson (JSON处理) +- Apache Commons +- Lombok + +## 5. 开发规范 + +### 5.1 代码规范 +- 遵循阿里巴巴Java开发手册 +- 使用统一的代码格式化配置 +- 必要的注释和文档 + +### 5.2 命名规范 +- 包名:com.qqchen.deploy.backend.workflow.* +- 类名:驼峰命名,见名知意 +- 方法名:动词开头,驼峰命名 +- 变量名:驼峰命名,有意义的名称 + +### 5.3 异常处理 +- 使用自定义异常类 +- 统一的错误码管理 +- 详细的错误信息 + +### 5.4 日志规范 +- 使用SLF4J + Logback +- 分级别记录日志 +- 包含必要的上下文信息 + +## 6. 部署要求 + +### 6.1 环境要求 +- JDK 17+ +- MySQL 8.0+ +- Maven 3.8+ + +### 6.2 配置要求 +- 数据库连接配置 +- 线程池配置 +- 日志配置 +- 缓存配置 + +### 6.3 监控要求 +- JVM监控 +- 数据库监控 +- 业务监控 +- 日志监控 + +## 7. API接口说明 + +### 7.1 工作流定义接口 + +#### 7.1.1 已实现接口 +- 暂无已实现的HTTP接口,但Service层已实现以下功能: + - 创建工作流定义 + - 更新工作流定义 + - 删除工作流定义 + - 查询工作流定义列表 + - 获取工作流定义详情 + +### 7.2 工作流实例接口 + +#### 7.2.1 已实现接口 +- 暂无已实现的HTTP接口,但Service层已实现以下功能: + - 创建工作流实例 + - 查询工作流实例列表 + - 获取工作流实例详情 + +### 7.3 节点实例接口 + +#### 7.3.1 已实现接口 +- 暂无已实现的HTTP接口,但Service层已实现以下功能: + - 查询节点实例列表 + - 获取节点实例详情 + +### 7.4 工作流变量接口 + +#### 7.4.1 已实现接口 +- 暂无已实现的HTTP接口,但Service层已实现以下功能: + - 查询工作流变量列表 + +### 7.5 工作流日志接口 + +#### 7.5.1 已实现接口 +- 暂无已实现的HTTP接口,但Service层已实现以下功能: + - 查询工作流日志列表 + +### 7.6 数据结构说明 + +#### 7.6.1 工作流定义相关 + +1. **WorkflowDefinitionDTO** +```java +{ + Long id; // 主键ID + String name; // 工作流名称 + String description; // 工作流描述 + WorkflowStatusEnum status; // 状态(DRAFT/PUBLISHED/DISABLED) + List nodes; // 节点配置列表 + List transitions; // 流转配置列表 + String createBy; // 创建人 + LocalDateTime createTime; // 创建时间 + String updateBy; // 更新人 + LocalDateTime updateTime; // 更新时间 + Integer version; // 版本号 +} +``` + +2. **NodeConfig** +```java +{ + String id; // 节点ID + String name; // 节点名称 + NodeTypeEnum type; // 节点类型 + Map config; // 节点配置(JSON) +} +``` + +3. **TransitionConfig** +```java +{ + String id; // 流转ID + String sourceNodeId; // 源节点ID + String targetNodeId; // 目标节点ID + String condition; // 流转条件 +} +``` + +#### 7.6.2 工作流实例相关 + +1. **WorkflowInstanceDTO** +```java +{ + Long id; // 主键ID + Long definitionId; // 工作流定义ID + String name; // 工作流名称 + WorkflowStatusEnum status; // 状态 + String createBy; // 创建人 + LocalDateTime createTime; // 创建时间 + String updateBy; // 更新人 + LocalDateTime updateTime; // 更新时间 +} +``` + +2. **NodeInstanceDTO** +```java +{ + Long id; // 主键ID + Long workflowInstanceId; // 工作流实例ID + String nodeId; // 节点定义ID + String nodeName; // 节点名称 + NodeTypeEnum nodeType; // 节点类型 + NodeStatusEnum status; // 节点状态 + LocalDateTime startTime; // 开始时间 + LocalDateTime endTime; // 结束时间 + String error; // 错误信息 +} +``` + +#### 7.6.3 工作流变量相关 + +1. **WorkflowVariableDTO** +```java +{ + Long id; // 主键ID + Long workflowInstanceId; // 工作流实例ID + Long nodeInstanceId; // 节点实例ID + String name; // 变量名 + String value; // 变量值 + VariableScopeEnum scope; // 作用域 +} +``` + +#### 7.6.4 工作流日志相关 + +1. **WorkflowLogDTO** +```java +{ + Long id; // 主键ID + Long workflowInstanceId; // 工作流实例ID + Long nodeInstanceId; // 节点实例ID + String level; // 日志级别 + String content; // 日志内容 + LocalDateTime createTime; // 创建时间 +} +``` + +### 7.7 枚举值说明 + +1. **WorkflowStatusEnum** +```java +{ + DRAFT, // 草稿 + PUBLISHED, // 已发布 + DISABLED // 已禁用 +} +``` + +2. **NodeTypeEnum** +```java +{ + START, // 开始节点 + END, // 结束节点 + TASK, // 任务节点 + GATEWAY // 网关节点 +} +``` + +3. **NodeStatusEnum** +```java +{ + PENDING, // 待执行 + RUNNING, // 执行中 + COMPLETED, // 已完成 + FAILED, // 执行失败 + SKIPPED // 已跳过 +} +``` + +4. **VariableScopeEnum** +```java +{ + GLOBAL, // 全局变量 + WORKFLOW, // 工作流级变量 + NODE // 节点级变量 +} +``` + +### 7.8 错误码说明 + +1. **系统错误 (1xxx)** +``` +1000: 系统内��错误 +1001: 数据库操作失败 +1002: 并发操作冲突 +``` + +2. **工作流定义错误 (2xxx)** +``` +2000: 工作流定义不存在 +2001: 工作流定义名称重复 +2002: 非法状态转换 +2003: 节点配置无效 +2004: 流转配置无效 +``` + +3. **工作流实例错误 (3xxx)** +``` +3000: 工作流实例不存在 +3001: 实例状态不允许操作 +3002: 节点执行失败 +3003: 变量不存在 +``` + +4. **权限错误 (4xxx)** +``` +4000: 无操作权限 +4001: 用户未认证 +4002: 用户未授权 +``` + +## 8. 详细设计 + +### 8.1 工作流引擎核心设计 + +#### 8.1.1 工作流定义解析 +1. **解析流程** +```java +WorkflowDefinition (JSON) -> WorkflowDefinitionParser -> RuntimeWorkflow +``` + +2. **主要组件** +- WorkflowDefinitionParser: 负责解析工作流定义 +- NodeParser: 负责解析节点配置 +- TransitionParser: 负责解析流转配置 +- ValidatorChain: 配置验证链 + +3. **验证规则** +- 节点完整性验证 +- 流转完整性验证 +- 起始节点验证 +- 结束节点验证 +- 环路检测 + +#### 8.1.2 工作流执行引擎 +1. **核心接口** +```java +public interface WorkflowEngine { + // 创建工作流实例 + WorkflowInstance createWorkflowInstance(Long definitionId, Map variables); + + // 执行工作流实例 + void executeWorkflowInstance(Long instanceId); + + // 暂停工作流实例 + void suspendWorkflowInstance(Long instanceId); + + // 恢复工作流实例 + void resumeWorkflowInstance(Long instanceId); + + // 终止工作流实例 + void terminateWorkflowInstance(Long instanceId); +} +``` + +2. **执行流程** +``` +开始 + ↓ +加载工作流定义 + ↓ +创建工作流上下文 + ↓ +执行开始节点 + ↓ +while (存在待执行节点) { + 获取下一个节点 + 执行节点 + 更新节点状态 + 处理节点输出 + 确定下一个节点 +} + ↓ +执行结束节点 + ↓ +结束 +``` + +3. **状态管理** +- 工作流状态机 +``` +PENDING → RUNNING → COMPLETED + ↓ ↓ ↑ + └→ FAILED ←┘ CANCELLED +``` + +- 节点状态机 +``` +PENDING → RUNNING → COMPLETED + ↓ ↓ ↑ + └→ FAILED ←┘ SKIPPED +``` + +#### 8.1.3 节点执行器 +1. **基础抽象类** +```java +public abstract class AbstractNodeExecutor implements NodeExecutor { + // 执行前处理 + protected void beforeExecute(NodeInstance node, WorkflowContext context); + + // 执行节点 + protected abstract ExecuteResult doExecute(NodeInstance node, WorkflowContext context); + + // 执行后处理 + protected void afterExecute(NodeInstance node, WorkflowContext context, ExecuteResult result); + + // 异常处理 + protected void handleException(NodeInstance node, WorkflowContext context, Exception e); +} +``` + +2. **内置执行器** +- StartNodeExecutor: 启动节点执行器 +- EndNodeExecutor: 结束节点执行器 +- TaskNodeExecutor: 任务节点执行器 +- GatewayNodeExecutor: 网关节点执行器 + +3. **任务执行器** +- ShellTaskExecutor: Shell命令执行器 +- HttpTaskExecutor: HTTP请求执行器 +- JavaTaskExecutor: Java代码执行器 + +#### 8.1.4 流转控制 +1. **流转规则引擎** +```java +public interface TransitionRuleEngine { + // 评估流转条件 + boolean evaluate(String condition, WorkflowContext context); + + // 获取下一个节点 + List getNextNodes(String currentNodeId, WorkflowContext context); +} +``` + +2. **条件表达式** +- 支持SpEL表达式 +- 支持变量引用 +- 支持函数调用 +- 支持逻辑运算 + +3. **网关类型** +- 排他网关(XOR):只选择一个分支 +- 并行网关(AND):并行执行所有分支 +- 包容网关(OR):选择满足条件的所有分支 + +### 8.2 数据模型设计 + +#### 8.2.1 工作流定义相关表 +1. **工作流定义表 (wf_workflow_definition)** +```sql +CREATE TABLE wf_workflow_definition ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + code VARCHAR(100) NOT NULL COMMENT '工作流编码', + name VARCHAR(100) NOT NULL COMMENT '工作流名称', + description VARCHAR(255) NULL COMMENT '工作流描述', + status VARCHAR(20) NOT NULL COMMENT '工作流状态', + version_no INT NOT NULL DEFAULT 1 COMMENT '版本号', + node_config TEXT NULL COMMENT '节点配置(JSON)', + transition_config TEXT NULL COMMENT '流转配置(JSON)', + form_definition TEXT NULL COMMENT '表单定义(JSON)', + graph_definition TEXT NULL COMMENT '图形信息(JSON)', + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', + + CONSTRAINT UK_workflow_definition_code_version UNIQUE (code, version_no) +); +``` + +2. **节点定义表 (wf_node_definition)** +```sql +CREATE TABLE wf_node_definition ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID', + node_id VARCHAR(100) NOT NULL COMMENT '节点ID', + name VARCHAR(100) NOT NULL COMMENT '节点名称', + type VARCHAR(20) NOT NULL COMMENT '节点类型', + config TEXT NULL COMMENT '节点配置(JSON)', + order_num INT NOT NULL DEFAULT 0 COMMENT '排序号', + + CONSTRAINT FK_node_definition_workflow FOREIGN KEY (workflow_definition_id) + REFERENCES wf_workflow_definition (id), + CONSTRAINT UK_node_definition_workflow_node UNIQUE (workflow_definition_id, node_id) +); +``` + +3. **节点类型表 (wf_node_type)** +```sql +CREATE TABLE wf_node_type ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + code VARCHAR(100) NOT NULL COMMENT '节点类型编码', + name VARCHAR(100) NOT NULL COMMENT '节点类型名称', + description TEXT NULL COMMENT '节点类型描述', + category VARCHAR(50) NOT NULL COMMENT '节点类型分类', + icon VARCHAR(100) NULL COMMENT '节点图标', + color VARCHAR(20) NULL COMMENT '节点颜色', + executors TEXT NULL COMMENT '执行器列表(JSON)', + config_schema TEXT NULL COMMENT '节点配置模式(JSON)', + default_config TEXT NULL COMMENT '默认配置(JSON)', + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', + + CONSTRAINT UK_node_type_code UNIQUE (code) +); +``` + +#### 8.2.2 工作流实例相关表 +1. **工作流实例表 (wf_workflow_instance)** +```sql +CREATE TABLE wf_workflow_instance ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID', + business_key VARCHAR(100) NOT NULL COMMENT '业务标识', + status VARCHAR(20) NOT NULL COMMENT '状态', + start_time DATETIME(6) NULL COMMENT '开始时间', + end_time DATETIME(6) NULL COMMENT '结束时间', + error TEXT NULL COMMENT '错误信息', + + CONSTRAINT FK_workflow_instance_definition FOREIGN KEY (workflow_definition_id) + REFERENCES wf_workflow_definition (id), + CONSTRAINT UK_workflow_instance_business_key UNIQUE (business_key) +); +``` + +2. **节点实例表 (wf_node_instance)** +```sql +CREATE TABLE wf_node_instance ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID', + node_id VARCHAR(100) NOT NULL COMMENT '节点ID', + node_type VARCHAR(20) NOT NULL COMMENT '节点类型', + name VARCHAR(100) NOT NULL COMMENT '节点名称', + status VARCHAR(20) NOT NULL COMMENT '状态', + start_time DATETIME(6) NULL COMMENT '开始时间', + end_time DATETIME(6) NULL COMMENT '结束时间', + config TEXT NULL COMMENT '节点配置(JSON)', + input TEXT NULL COMMENT '输入参���(JSON)', + output TEXT NULL COMMENT '输出结果(JSON)', + error TEXT NULL COMMENT '错误信息', + pre_node_id VARCHAR(100) NULL COMMENT '前置节点ID', + + CONSTRAINT FK_node_instance_workflow FOREIGN KEY (workflow_instance_id) + REFERENCES wf_workflow_instance (id) +); +``` + +#### 8.2.3 工作流变量表 (wf_workflow_variable) +```sql +CREATE TABLE wf_workflow_variable ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID', + name VARCHAR(100) NOT NULL COMMENT '变量名', + value TEXT NULL COMMENT '变量值', + type VARCHAR(20) NOT NULL COMMENT '变量类型', + + CONSTRAINT FK_workflow_variable_instance FOREIGN KEY (workflow_instance_id) + REFERENCES wf_workflow_instance (id), + CONSTRAINT UK_workflow_variable_instance_name UNIQUE (workflow_instance_id, name) +); +``` + +#### 8.2.4 工作流日志表 (wf_log) +```sql +CREATE TABLE wf_log ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + workflow_instance_id BIGINT NOT NULL, + node_id VARCHAR(50), + message VARCHAR(1000) NOT NULL, + level VARCHAR(20) NOT NULL, + detail TEXT, + create_time DATETIME NOT NULL, + create_by VARCHAR(50), + update_time DATETIME, + update_by VARCHAR(50), + version INT DEFAULT 0, + deleted BOOLEAN DEFAULT FALSE +); + +-- 索引 +CREATE INDEX idx_wf_log_instance ON wf_log (workflow_instance_id); +CREATE INDEX idx_wf_log_node ON wf_log (workflow_instance_id, node_id); +``` + +#### 8.2.5 工作流权限表 (wf_workflow_permission) +```sql +CREATE TABLE wf_workflow_permission ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', + workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID', + type VARCHAR(50) NOT NULL COMMENT '权限类型', + user_id BIGINT COMMENT '用户ID', + role_id BIGINT COMMENT '角色ID', + department_id BIGINT COMMENT '部门ID', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + create_by BIGINT NOT NULL COMMENT '创建人', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + update_by BIGINT NOT NULL COMMENT '更新人', + version INT NOT NULL DEFAULT 0 COMMENT '版本号', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + + INDEX idx_workflow_definition_id (workflow_definition_id), + INDEX idx_user_id (user_id), + INDEX idx_role_id (role_id), + INDEX idx_department_id (department_id) +); +``` + +### 8.3 核心功能设计 + +#### 8.3.1 版本控制 +1. **版本策略** +- 主版本号:重大更新 +- 次版本号:功能更新 +- 修订版本号:Bug修复 + +2. **版本管理** +- 草稿版本:可以修改 +- 发布版本:只读 +- 禁用版本:不可用 + +3. **版本操作** +- 创建新版本 +- 复制版本 +- 回滚版本 +- 删除版本 + +#### 8.3.2 变量管理 +1. **变量作用域** +- 全局变量:整个工作流可见 +- 工作流变量:当前工作流实例可见 +- 节点变量:当前节点可见 + +2. **变量生命周期** +- 创建:工作流启动时 +- 更新:节点执行时 +- 销毁:工作流结束时 + +3. **变量操作** +- 设置变量 +- 获取变量 +- 删除变量 +- 清空变量 + +#### 8.3.3 任务执行 +1. **Shell任务** +```java +public class ShellTaskExecutor implements TaskExecutor { + ExecuteResult execute(TaskConfig config) { + // 1. 解析命令 + String command = config.getCommand(); + + // 2. 设置环境变量 + Map env = config.getEnvironment(); + + // 3. 执行命令 + Process process = Runtime.getRuntime().exec(command); + + // 4. 获取结果 + String output = readOutput(process); + + // 5. 检查退出码 + int exitCode = process.waitFor(); + + return new ExecuteResult(exitCode == 0, output); + } +} +``` + +2. **HTTP任务** +```java +public class HttpTaskExecutor implements TaskExecutor { + ExecuteResult execute(TaskConfig config) { + // 1. 构建请求 + HttpRequest request = buildRequest(config); + + // 2. 发送请求 + HttpResponse response = httpClient.send(request); + + // 3. 处理响应 + String body = response.body(); + + // 4. 检查状态码 + boolean success = response.statusCode() >= 200 && response.statusCode() < 300; + + return new ExecuteResult(success, body); + } +} +``` + +3. **Java任务** +```java +public class JavaTaskExecutor implements TaskExecutor { + ExecuteResult execute(TaskConfig config) { + // 1. 获取类名和方法名 + String className = config.getClassName(); + String methodName = config.getMethodName(); + + // 2. 加载类 + Class clazz = Class.forName(className); + + // 3. 获取方法 + Method method = clazz.getMethod(methodName); + + // 4. 执行方法 + Object result = method.invoke(null); + + return new ExecuteResult(true, result.toString()); + } +} +``` + +#### 8.3.4 错误处理 +1. **重试机制** +```java +public class RetryableTaskExecutor implements TaskExecutor { + ExecuteResult execute(TaskConfig config) { + int maxAttempts = config.getMaxAttempts(); + long delay = config.getDelay(); + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return delegate.execute(config); + } catch (Exception e) { + if (attempt == maxAttempts) { + throw e; + } + Thread.sleep(delay); + } + } + } +} +``` + +2. **补偿机制** +```java +public class CompensableTaskExecutor implements TaskExecutor { + ExecuteResult execute(TaskConfig config) { + try { + ExecuteResult result = delegate.execute(config); + if (!result.isSuccess()) { + compensate(config); + } + return result; + } catch (Exception e) { + compensate(config); + throw e; + } + } + + void compensate(TaskConfig config) { + // 执行补偿逻辑 + } +} +``` + +3. **超时控制** +```java +public class TimeoutTaskExecutor implements TaskExecutor { + ExecuteResult execute(TaskConfig config) { + Future future = executor.submit(() -> { + return delegate.execute(config); + }); + + try { + return future.get(config.getTimeout(), TimeUnit.SECONDS); + } catch (TimeoutException e) { + future.cancel(true); + throw new TaskTimeoutException(); + } + } +} +``` + +### 8.4 扩展点设计 + +#### 8.4.1 节点类型扩展 +1. **自定义节点** +```java +@Component +public class CustomNodeExecutor extends AbstractNodeExecutor { + @Override + protected ExecuteResult doExecute(NodeInstance node, WorkflowContext context) { + // 实现自定义节点逻辑 + } +} +``` + +2. **节点注册** +```java +public class NodeExecutorRegistry { + private Map executors = new HashMap<>(); + + public void register(String type, NodeExecutor executor) { + executors.put(type, executor); + } + + public NodeExecutor get(String type) { + return executors.get(type); + } +} +``` + +#### 8.4.2 表达式扩展 +1. **自定义函数** +```java +public class CustomFunctions { + @Function + public static boolean checkCondition(String value) { + // 实现自定义函数逻辑 + } +} +``` + +2. **函数注册** +```java +public class FunctionRegistry { + private Map functions = new HashMap<>(); + + public void register(String name, Method method) { + functions.put(name, method); + } + + public Method get(String name) { + return functions.get(name); + } +} +``` + +#### 8.4.3 变量处理扩展 +1. **自定义变量处理器** +```java +public class CustomVariableHandler implements VariableHandler { + @Override + public Object serialize(Object value) { + // 实现序列化逻辑 + } + + @Override + public Object deserialize(String value, Class type) { + // 实现反序列化逻辑 + } +} +``` + +2. **处理器注册** +```java +public class VariableHandlerRegistry { + private Map, VariableHandler> handlers = new HashMap<>(); + + public void register(Class type, VariableHandler handler) { + handlers.put(type, handler); + } + + public VariableHandler get(Class type) { + return handlers.get(type); + } +} +``` + +### 8.5 性能优化设计 + +#### 8.5.1 数据库优化 +1. **索引设计** +```sql +-- 工作流定义表索引 +ALTER TABLE workflow_definition ADD INDEX idx_code (code); +ALTER TABLE workflow_definition ADD INDEX idx_status (status); + +-- 工作流实例表索引 +ALTER TABLE workflow_instance ADD INDEX idx_definition_id (definition_id); +ALTER TABLE workflow_instance ADD INDEX idx_status (status); +ALTER TABLE workflow_instance ADD INDEX idx_create_time (create_time); + +-- 节点实例表索引 +ALTER TABLE node_instance ADD INDEX idx_workflow_instance_id (workflow_instance_id); +ALTER TABLE node_instance ADD INDEX idx_status (status); + +-- 工作流变量表索引 +ALTER TABLE workflow_variable ADD INDEX idx_workflow_instance_id (workflow_instance_id); +ALTER TABLE workflow_variable ADD INDEX idx_scope (scope); +``` + +2. **分表策略** +- 按时间分表 +- 按工作流类型分表 +- 按租户分表 + +3. **归档策略** +- 定期归档历史数据 +- 按时间维度归档 +- 支持归档数据查询 + +#### 8.5.2 缓存设计 +1. **缓存层次** +``` +应用缓存 → 分布式缓存 → 数据库 +``` + +2. **缓存对���** +- 工作流定义 +- 节点配置 +- 常用变量 +- 执行统计 + +3. **缓存策略** +- LRU淘汰 +- 定时刷新 +- 主动失效 + +#### 8.5.3 并发控制 +1. **锁设计** +```java +public interface LockManager { + // 获取锁 + boolean acquireLock(String key, long timeout); + + // 释放锁 + void releaseLock(String key); + + // 续期锁 + boolean renewLock(String key); +} +``` + +2. **隔离级别** +- 工作流实例级 +- 节点实例级 +- 变量级 + +3. **并发策略** +- 乐观锁 +- 悲观锁 +- 分布式锁 + +### 8.6 监控运维设计 + +#### 8.6.1 监控指标 +1. **性能指标** +- 工作流执行时间 +- 节点执行时间 +- 资源使用率 +- 并发数量 + +2. **业务指标** +- 工作流成功率 +- 节点成功率 +- 重试次数 +- 超次数 + +3. **系统指标** +- CPU使用率 +- 内存使用率 +- 磁盘使用率 +- 网络使用率 + +#### 8.6.2 告警设计 +1. **告警规则** +- 执行超时 +- 异常失败 +- 资源不足 +- 并发超限 + +2. **告警级别** +- 严重告警 +- 警告告警 +- 提示告警 + +3. **告警方式** +- 邮件通知 +- 短信通知 +- 钉钉通知 +- Webhook通知 + +#### 8.6.3 日志设计 +1. **日志分类** +- 系统日志 +- 业务日志 +- 审计日志 +- 性能日志 + +2. **日志格式** +```json +{ + "timestamp": "2023-12-05 10:00:00", + "level": "INFO", + "thread": "main", + "class": "WorkflowEngine", + "message": "工作流开始执行", + "context": { + "workflowInstanceId": "123", + "nodeId": "node1", + "variables": {} + } +} +``` + +3. **日志存储** +- 文件存储 +- 数据库存储 +- ES存储 + +### 8.7 安全设计 + +#### 8.7.1 认证授权 +1. **认证方式** +- 用户名密码 +- Token认证 +- OAuth2认证 +- SSO认证 + +2. **权限模型** +- 角色权限 +- 数据权限 +- 功能权限 +- 字段权限 + +3. **权限控制** +```java +@PreAuthorize("hasPermission('workflow', 'execute')") +public void executeWorkflow(Long instanceId) { + // 执行工作流 +} +``` + +#### 8.7.2 数据安全 +1. **敏感数据** +- 配置信息 +- 执行结果 +- 变量数据 +- 日志内容 + +2. **加密方案** +- 配置加密 +- 传输加密 +- 存储加密 +- 字段加密 + +3. **脱敏策略** +```java +public class DataMaskingUtils { + public static String maskSensitiveData(String data, String type) { + switch (type) { + case "mobile": + return maskMobile(data); + case "email": + return maskEmail(data); + case "idcard": + return maskIdCard(data); + default: + return data; + } + } +} +``` + +#### 8.7.3 审计日志 +1. **审计内容** +- 操作人 +- 操作时间 +- 操作类型 +- 操作内容 +- 操作结果 + +2. **审计方式** +```java +@Audit(type = "workflow", operation = "execute") +public void executeWorkflow(Long instanceId) { + // 执行工作流 +} +``` + +3. **审计存储** +```sql +CREATE TABLE audit_log ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id VARCHAR(50) NOT NULL COMMENT '用户ID', + operation_type VARCHAR(50) NOT NULL COMMENT '操作类型', + operation_time DATETIME NOT NULL COMMENT '操作时间', + resource_type VARCHAR(50) NOT NULL COMMENT '资源类型', + resource_id VARCHAR(50) NOT NULL COMMENT '资源ID', + operation_content TEXT COMMENT '操作内容', + operation_result VARCHAR(20) COMMENT '操作结果', + client_ip VARCHAR(50) COMMENT '客户端IP', + user_agent VARCHAR(200) COMMENT '用户代理' +); +``` + +## 9. 接口实现状态 + +### 9.1 工作流定义服务 (IWorkflowDefinitionService) + +#### 9.1.1 已实现接口 + +1. **创建工作流定义** +```java +WorkflowDefinitionDTO create(WorkflowDefinitionDTO dto) +``` +- 功能:创建新的工作流定义 +- 参数: + - dto: 工作流定义数据,包含名称、描述、节点配置等 +- 返回:创建后的工作流定义 +- 说明: + - 自动设置为草稿状态 + - 检查编码唯一性 + - 启用状态默认为true + +2. **更新工作流定义** +```java +WorkflowDefinitionDTO update(Long id, WorkflowDefinitionDTO dto) +``` +- 功能:更新现有工作流定义 +- 参数: + - id: 工作流定义ID + - dto: 更新的数据 +- 返回:更新后的工作流定义 +- 说明: + - 仅草稿状态可更新 + - 检查编码唯一性 + - 支持更新基本信息、节点配置、流转配置等 + +3. **发布工作流定义** +```java +WorkflowDefinitionDTO publish(Long id) +``` +- 功能:发布工作流定义 +- 参数: + - id: 工作流定义ID +- 返回:发布后的工作流定义 +- 说明: + - 仅草稿状态可发布 + - 发布前进行配置验证 + - 发布后状态变更为PUBLISHED + +4. **禁用工作流定义** +```java +WorkflowDefinitionDTO disable(Long id) +``` +- 功能:禁用工作流定义 +- 参数: + - id: 工作流定义ID +- 返回:禁用后的工作流定义 +- 说明: + - 仅已发布状态可禁用 + - 禁用后状态变更为DISABLED + +5. **启用工作流定义** +```java +WorkflowDefinitionDTO enable(Long id) +``` +- 功能:启用工作流定义 +- 参数: + - id: 工作流定义ID +- 返回:启用后的工作流定义 +- 说明: + - 仅禁用状态可启用 + - 启用后状态变更为PUBLISHED + +6. **创建新版本** +```java +WorkflowDefinitionDTO createNewVersion(Long id) +``` +- 功能:基于现有工作流定义创建新版本 +- 参数: + - id: 源工作流定义ID +- 返回:新版本的工作流定义 +- 说明: + - 复制所有配置信息 + - 版本号自动递增 + - 新版本为草稿状态 + +7. **查询相关接口** +```java +WorkflowDefinitionDTO findLatestByCode(String code) +WorkflowDefinitionDTO findByCodeAndVersion(String code, Integer version) +List findAllVersions(String code) +``` +- 功能:查询工作流定义 +- 说明: + - 支持查询最新版本 + - 支持查询指定版本 + - 支持查询所有版本 + +8. **验证工作流定义** +```java +boolean validate(Long id) +``` +- 功能:验证工作流定义的完整性和正确性 +- 参数: + - id: 工作流定义ID +- 返回:验证结果 +- 说明: + - 验证节点配置完整性 + - 验证流转配置完整性 + - 验证表单定义完整性 + - 验证图形信息完整性 + +### 9.2 工作流实例服务 (IWorkflowInstanceService) + +#### 9.2.1 已实现接口 + +1. **基础CRUD接口** +- 继承自BaseService,包含基础的增删改查功能 + +#### 9.2.2 待实现接口 + +1. **创建工作流实例** +```java +WorkflowInstanceDTO createInstance(Long definitionId, Map variables) +``` +- 功能:创建工作流实例 +- 参数: + - definitionId: 工作流定义ID + - variables: 初始变量 +- 返回:创建的工作流实例 + +2. **启动工作流实例** +```java +void startInstance(Long instanceId) +``` +- 功能:启动工作流实例 +- 参数: + - instanceId: 实例ID + +3. **暂停工作流实例** +```java +void suspendInstance(Long instanceId) +``` +- 功能:暂停工作流实例 +- 参数: + - instanceId: 实例ID + +4. **恢复工作流实例** +```java +void resumeInstance(Long instanceId) +``` +- 功能:恢复工作流实例 +- 参数: + - instanceId: 实例ID + +5. **终止工作流实例** +```java +void terminateInstance(Long instanceId) +``` +- 功能:终止工作流实例 +- 参数: + - instanceId: 实例ID + +### 9.3 节点实例服务 (INodeInstanceService) + +#### 9.3.1 已实现接口 + +1. **查询节点实例** +```java +List findByWorkflowInstanceId(Long workflowInstanceId) +List findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status) +``` +- 功能:查询节点实例列表 +- 参数: + - workflowInstanceId: 工作流实例ID + - status: 节点状态(可选) +- 返回:节点实例列表 + +2. **更新节点状态** +```java +void updateStatus(Long id, NodeStatusEnum status, String output, String error) +``` +- 功能:更新节点状态 +- 参数: + - id: 节点实例ID + - status: 新状态 + - output: 输出结果 + - error: 错误信息 +- 说明: + - 自动记录状态变更时间 + - 支持记录执行结果和错误信息 + +### 9.4 工作流变量服务 (IWorkflowVariableService) + +#### 9.4.1 已实现接口 + +1. **设置变量** +```java +void setVariable(Long workflowInstanceId, String name, Object value) +``` +- 功能:设置工作流变量 +- 参数: + - workflowInstanceId: 工作流实例ID + - name: 变量名 + - value: 变量值 +- 说明: + - 支持任意类型的变量值 + - 自动序列化和类型记录 + - 支持变量更新 + +2. **获取变量** +```java +Map getVariables(Long workflowInstanceId) +Map getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope) +``` +- 功能:获取工作流变量 +- 参数: + - workflowInstanceId: 工作流实例ID + - scope: 变量作用域(可选) +- 返回:变量Map +- 说明: + - 支持获取所有变量 + - 支持按作用域获取变量 + - 自动反序列化为原始类型 + +3. **删除变量** +```java +void deleteVariables(Long workflowInstanceId) +``` +- 功能:删除工作流变量 +- 参数: + - workflowInstanceId: 工作流实例ID +- 说明: + - 删除指定实例的所有变量 + +### 9.5 工作流日志服务 (IWorkflowLogService) + +#### 9.5.1 已实现接口 + +1. **记录日志** +```java +void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail) +``` +- 功能:记录工作流日志 +- 参数: + - workflowInstanceId: 工作流实例ID + - nodeId: 节点ID(可选) + - message: 日志消息 + - level: 日志级别 + - detail: 详细信息 +- 说明: + - 支持不同级别的日志 + - 支持节点级别的日志 + - 支持详细信息记录 + +2. **查询日志** +```java +List getLogs(Long workflowInstanceId) +List getNodeLogs(Long workflowInstanceId, String nodeId) +``` +- 功能:查询工作流日志 +- 参数: + - workflowInstanceId: 工作流实例ID + - nodeId: 节点ID(可选) +- 返回:日志列表 +- 说明: + - 支持查询实例所有日志 + - 支持查询节点级别日志 + +3. **删除日志** +```java +void deleteLogs(Long workflowInstanceId) +``` +- 功能:删除工作流日志 +- 参数: + - workflowInstanceId: 工作流实例ID +- 说明: + - 删除指定实例的所有日志 + +### 9.6 节点类型服务 (INodeTypeService) + +#### 9.6.1 已实现接口 + +1. **查询节点类型** +```java +NodeTypeDTO findByCode(String code) +``` +- 功能:根据编码查询节点类型 +- 参数: + - code: 节点类型编码 +- 返回:节点类型信息 + +2. **获取执行器列表** +```java +List getExecutors(String code) +``` +- 功能:获取节点类型支持的执行器列表 +- 参数: + - code: 节点类型编码 +- 返回:执行器定义列表 +- 说明: + - 仅任务节点类型支持 + - 返回执行器配置模式 + - 支持多种执行器 + +3. **启用/禁用节点类型** +```java +void enable(Long id) +void disable(Long id) +``` +- 功能:启用或禁用节点类型 +- 参数: + - id: 节点类型ID +- 说明: + - 控制节点类型是否可用 + - 影响工作流设计时的节点选择 + +### 9.7 待实现功能 + +1. **工作流实例管理** +- 实例暂停/恢复机制 +- 实例强制终止 +- 实例重试机制 +- 子流程支持 + +2. **节点执行增强** +- 节点重试机制 +- 节点超时控制 +- 节点执行补偿 +- 自定义节点扩展 + +3. **变量管理增强** +- 变量作用域隔离 +- 变量类型转换 +- 变量生命周期管理 +- 变量访问控制 + +4. **监控和统计** +- 执行状态监控 +- 性能统计分析 +- 告警机制 +- 审计日志 + +5. **权限控制** +- 工作流权限管理 +- 节点权限控制 +- 变量访问权限 +- 操作审计 \ No newline at end of file diff --git a/frontend/api.md b/frontend/api.md new file mode 100644 index 00000000..9e674001 --- /dev/null +++ b/frontend/api.md @@ -0,0 +1,659 @@ +# API 接口文档 + +## 1. 通用说明 + +### 1.1 接口规范 + +- 基础路径: `/api/v1` +- 请求方式: REST风格 +- 数据格式: JSON +- 字符编码: UTF-8 +- 时间格式: ISO8601 (YYYY-MM-DDTHH:mm:ss.SSSZ) + +### 1.2 通用响应格式 + +```json +{ + "code": 0, // 响应码,0表示成功,非0表示失败 + "message": "成功", // 响应消息 + "data": { // 响应数据 + // 具体数据结构 + } +} +``` + +### 1.3 通用查询参数 + +分页查询参数: +```json +{ + "page": 1, // 页码,从1开始 + "size": 10, // 每页大小 + "sort": ["id,desc"], // 排序字段,可选 + "keyword": "", // 关键字搜索,可选 +} +``` + +### 1.4 通用错误码 + +| 错误码 | 说明 | 处理建议 | +|--------|------|----------| +| 0 | 成功 | - | +| 1000 | 系统内部错误 | 联系管理员 | +| 1001 | 数据库操作失败 | 重试或联系管理员 | +| 1002 | 并发操作冲突 | 刷新后重试 | +| 2000 | 参数验证失败 | 检查参数 | +| 2001 | 数据不存在 | 检查参数 | +| 2002 | 数据已存在 | 检查参数 | + +## 2. 工作流定义��口 + +### 2.1 创建工作流定义 + +**接口说明**:创建新的工作流定义 + +**请求路径**:POST /workflow-definitions + +**请求参数**: +```json +{ + "code": "string", // 工作流编码,必填,唯一 + "name": "string", // 工作流名称,必填 + "description": "string", // 描述,可选 + "nodeConfig": { // 节点配置,必填 + "nodes": [{ + "id": "string", // 节点ID + "type": "string", // 节点类型 + "name": "string", // 节点名称 + "config": { // 节点配置 + // 具体配置项根据节点类型定义 + } + }] + }, + "transitionConfig": { // 流转配置,必填 + "transitions": [{ + "from": "string", // 来源节点ID + "to": "string", // 目标节点ID + "condition": "string" // 流转条件 + }] + }, + "formDefinition": { // 表单定义,必填 + // 表单配置项 + }, + "graphDefinition": { // 图形定义,必填 + // 图形布局配置 + } +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "id": "long", // 工作流定义ID + "code": "string", // 工作流编码 + "name": "string", // 工作流名称 + "description": "string", // 描述 + "version": 1, // 版本号 + "status": "DRAFT", // 状态:DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用 + "enabled": true, // 是否启用 + "nodeConfig": {}, // 节点配置 + "transitionConfig": {}, // 流转配置 + "formDefinition": {}, // 表单定义 + "graphDefinition": {}, // 图形定义 + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + } +} +``` + +### 2.2 更新工作流定义 + +**接口说明**:更新工作流定义,仅草稿状态可更新 + +**请求路径**:PUT /workflow-definitions/{id} + +**路径参数**: +- id: 工作流定义ID + +**请求参数**:同创建接口 + +**响应数据**:同创建接口 + +### 2.3 发布工作流定义 + +**接口说明**:发布工作流定义,使其可被使用 + +**请求路径**:POST /workflow-definitions/{id}/publish + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.4 禁用工作流定义 + +**接口说明**:禁用工作流定义,禁止新建实例 + +**请求路径**:POST /workflow-definitions/{id}/disable + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.5 启用工作流定义 + +**接口说明**:启用已��用的工作流定义 + +**请求路径**:POST /workflow-definitions/{id}/enable + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.6 创建新版本 + +**接口说明**:基于现有工作流定义创建新版本 + +**请求路径**:POST /workflow-definitions/{id}/versions + +**路径参数**: +- id: 工作流定义ID + +**响应数据**:同创建接口 + +### 2.7 查询工作流定义列表 + +**接口说明**:分页查询工作流定义列表 + +**请求路径**:GET /workflow-definitions + +**请求参数**: +```json +{ + "page": 1, // 页码,从1开始 + "size": 10, // 每页大小 + "sort": ["id,desc"], // 排序字段 + "keyword": "", // 关键字搜索 + "status": "DRAFT", // 状态过滤 + "enabled": true // 是否启用 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "content": [{ // 列表数据 + // 工作流定义数据,同创建接口 + }], + "totalElements": 100, // 总记录数 + "totalPages": 10, // 总页数 + "size": 10, // 每页大小 + "number": 1 // 当前页码 + } +} +``` + +## 3. 工作流实例接口 + +### 3.1 创建工作流实例 + +**接口说明**:创建工作流实例 + +**请求路径**:POST /workflow-instances + +**请求参数**: +```json +{ + "definitionId": "long", // 工作流定义ID,必填 + "businessKey": "string", // 业务标识,可选 + "variables": { // 初始变量,可选 + "key": "value" + } +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "id": "long", // 实例ID + "definitionId": "long", // 定义ID + "businessKey": "string",// 业务标识 + "status": "CREATED", // 状态:CREATED-已创建、RUNNING-运行中、SUSPENDED-已暂停、COMPLETED-已完成、TERMINATED-已终止 + "startTime": "string", // 开始时间 + "endTime": "string", // 结束时间 + "variables": {}, // 变量 + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + } +} +``` + +### 3.2 启动工作流实例 + +**接口说明**:启动工作流实例 + +**请求路径**:POST /workflow-instances/{id}/start + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.3 暂停工作流实例 + +**接口说明**:暂停运行中的工作流实例 + +**请求路径**:POST /workflow-instances/{id}/suspend + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.4 恢复工作流实例 + +**接口说明**:恢复已暂停的工作流实例 + +**请求路径**:POST /workflow-instances/{id}/resume + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.5 终止工作流实例 + +**接口说明**:强制终止工作流实例 + +**请求路径**:POST /workflow-instances/{id}/terminate + +**路径参数**: +- id: 实例ID + +**响应数据**:同创建接口 + +### 3.6 查询工作流实例列表 + +**接口说明**:分页查询工作流实例列表 + +**请求路径**:GET /workflow-instances + +**请求参数**: +```json +{ + "page": 1, // 页码,从1开始 + "size": 10, // 每页大小 + "sort": ["id,desc"], // 排序字段 + "keyword": "", // 关键字搜索 + "status": "RUNNING", // 状态过滤 + "definitionId": "long", // 定义ID过滤 + "businessKey": "string" // 业务标识过滤 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "content": [{ // 列表数据 + // 工作流实例数据,同创建接口 + }], + "totalElements": 100, // 总记录数 + "totalPages": 10, // 总页数 + "size": 10, // 每页大小 + "number": 1 // 当前页码 + } +} +``` + +## 4. 节点实例接口 + +### 4.1 查询节点实例列表 + +**接口说明**:查询工作流实例的节点列表 + +**请求路径**:GET /workflow-instances/{instanceId}/nodes + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "status": "RUNNING" // 状态过滤,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "id": "long", // 节点实例ID + "workflowInstanceId": "long", // 工作流实例ID + "nodeId": "string", // 节点定义ID + "nodeName": "string", // 节点名称 + "nodeType": "string", // 节点类型 + "status": "string", // 状态:CREATED、RUNNING、COMPLETED、FAILED + "startTime": "string", // 开始时间 + "endTime": "string", // 结束时间 + "output": "string", // 输出结果 + "error": "string", // 错误信息 + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + }] +} +``` + +## 5. 工作流变量接口 + +### 5.1 设置变量 + +**接口说明**:设置工作流实例变量 + +**请求路径**:POST /workflow-instances/{instanceId}/variables + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "name": "string", // 变量名,必填 + "value": "object", // 变量值,必�� + "scope": "GLOBAL" // 作用域:GLOBAL-全局、NODE-节点,可选,默认GLOBAL +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功" +} +``` + +### 5.2 获取变量 + +**接口说明**:获取工作流实例变量 + +**请求路径**:GET /workflow-instances/{instanceId}/variables + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "scope": "GLOBAL" // 作用域过滤,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": { + "variableName": "variableValue" + } +} +``` + +## 6. 工作流日志接口 + +### 6.1 查询日志列表 + +**接口说明**:查询工作流实例日志 + +**请求路径**:GET /workflow-instances/{instanceId}/logs + +**路径参数**: +- instanceId: 工作流实例ID + +**请求参数**: +```json +{ + "nodeId": "string", // 节点ID过滤,可选 + "level": "INFO", // 日志级别过滤:DEBUG、INFO、WARN、ERROR,可选 + "startTime": "string", // 开始时间过滤,可选 + "endTime": "string" // 结束时间过滤,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "id": "long", // 日志ID + "workflowInstanceId": "long", // 工作流实例ID + "nodeId": "string", // 节点ID + "level": "string", // 日志级别 + "content": "string", // 日志内容 + "detail": "string", // 详细信息 + "createTime": "string" // 创建时间 + }] +} +``` + +## 7. 节点类型接口 + +### 7.1 查询节点类型列表 + +**接口说明**:查询可用的节点类型列表 + +**请求路径**:GET /node-types + +**请求参数**: +```json +{ + "enabled": true, // 是否启用过滤,可选 + "category": "TASK" // 类型分类过滤:TASK、EVENT、GATEWAY,可选 +} +``` + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "id": "long", // 节点类型ID + "code": "string", // 节点类型编码 + "name": "string", // 节点类型名称 + "category": "string", // 分类 + "description": "string", // 描述 + "enabled": true, // 是否启用 + "icon": "string", // 图标 + "color": "string", // 颜色 + "executors": [{ // 执行器列表(仅TASK类型) + "code": "string", // 执行器编码 + "name": "string", // 执行器名称 + "description": "string", // 描述 + "configSchema": {} // 配置模式 + }], + "createTime": "string", // 创建时间 + "updateTime": "string" // 更新时间 + }] +} +``` + +### 7.2 获取节点执行器列表 + +**接口说明**:获取指定节点类型支持的执行器列表 + +**请求路径**:GET /node-types/{code}/executors + +**路径参数**: +- code: 节点类型编码 + +**响应数据**: +```json +{ + "code": 0, + "message": "成功", + "data": [{ + "code": "string", // 执行器编码 + "name": "string", // 执行器名称 + "description": "string", // 描述 + "configSchema": { // 配置模式 + "type": "object", + "properties": { + // 具体配置项定义 + }, + "required": [] // 必填项 + } + }] +} +``` + +## 8. 错误码说明 + +### 8.1 系统错误 (1xxx) +- 1000: 系统内部错误 +- 1001: 数据库操作失败 +- 1002: 并发操作冲突 + +### 8.2 通用业务错误 (2xxx) +- 2000: 参数验证失败 +- 2001: 数据不存在 +- 2002: 数据已存在 + +### 8.3 工作流定义错误 (3xxx) +- 3000: 工作流定义不存在 +- 3001: 工作流编码已存在 +- 3002: 工作流定义非草稿状态 +- 3003: 工作流定义未发布 +- 3004: 工作流定义已禁用 +- 3005: 节点配置无效 +- 3006: 流转配置无效 +- 3007: 表单配置无效 + +### 8.4 工作流实例错误 (4xxx) +- 4000: 工作流实例不存在 +- 4001: 工作流实例状态无效 +- 4002: 工作流实例已终止 +- 4003: 节点实例不存在 +- 4004: 变量类型无效 + +## 9. 数据结构说明 + +### 9.1 工作流状态 + +```typescript +enum WorkflowStatus { + DRAFT = "DRAFT", // 草稿 + PUBLISHED = "PUBLISHED", // 已发布 + DISABLED = "DISABLED" // 已禁用 +} +``` + +### 9.2 实例状态 + +```typescript +enum InstanceStatus { + CREATED = "CREATED", // 已创建 + RUNNING = "RUNNING", // 运行中 + SUSPENDED = "SUSPENDED", // 已暂停 + COMPLETED = "COMPLETED", // 已完成 + TERMINATED = "TERMINATED"// 已终止 +} +``` + +### 9.3 节点状态 + +```typescript +enum NodeStatus { + CREATED = "CREATED", // 已创建 + RUNNING = "RUNNING", // 运行中 + COMPLETED = "COMPLETED", // 已完成 + FAILED = "FAILED" // 失败 +} +``` + +### 9.4 日志级别 + +```typescript +enum LogLevel { + DEBUG = "DEBUG", + INFO = "INFO", + WARN = "WARN", + ERROR = "ERROR" +} +``` + +### 9.5 变量作用域 + +```typescript +enum VariableScope { + GLOBAL = "GLOBAL", // 全局变量 + NODE = "NODE" // 节点变量 +} +``` + +## 10. 最佳实践 + +### 10.1 工作流设计 + +1. 工作流定义创建流程: + - 创建草稿 + - 配置节点和流转 + - 验证配置 + - 发布使用 + +2. 节点类型选择: + - 根据业务场景选择合适的节点类型 + - 配置合适的执行器 + - 设置必要的变量 + +### 10.2 实例管理 + +1. 实例生命周期管理: + - 创建 -> 启动 -> 运行 -> 完成 + - 必要时可暂停或终止 + - 记录关键节点日志 + +2. 变量使用: + - 合理使用变量作用域 + - 及时清理无用变量 + - 注意变量类型匹配 + +### 10.3 错误处理 + +1. 异常处理: + - 捕获所有可能的错误码 + - 提供友好的错误提示 + - 记录详细的错误日志 + +2. 重试机制: + - 对非致命错误进行重试 + - 设置合理的重试间隔 + - 限制最大重试次数 + +### 10.4 性能优化 + +1. 查询优化: + - 合理使用分页 + - 避免大量数据查询 + - 使用合适的查询条件 + +2. 缓存使用: + - 缓存常用数据 + - 及时更新缓存 + - 避免缓存穿透 \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a2550cb9..6d996124 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,13 @@ "@ant-design/icons": "^5.2.6", "@antv/layout": "^1.2.14-beta.8", "@antv/x6": "^2.18.1", + "@antv/x6-plugin-clipboard": "^2.1.6", + "@antv/x6-plugin-export": "^2.1.6", + "@antv/x6-plugin-history": "^2.2.4", + "@antv/x6-plugin-keyboard": "^2.2.3", + "@antv/x6-plugin-selection": "^2.2.2", + "@antv/x6-plugin-snapline": "^2.1.7", + "@antv/x6-plugin-transform": "^2.1.8", "@antv/x6-react-shape": "^2.2.3", "@logicflow/core": "^2.0.9", "@logicflow/extension": "^2.0.13", @@ -20,6 +27,7 @@ "dagre": "^0.8.5", "form-render": "^2.5.1", "react": "^18.2.0", + "react-diff-viewer-continued": "^3.4.0", "react-dom": "^18.2.0", "react-redux": "^9.0.4", "react-router-dom": "^6.21.0" @@ -35,6 +43,7 @@ "eslint": "^8.55.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "less": "^4.2.1", "typescript": "^5.3.3", "vite": "^5.0.8" } @@ -193,6 +202,7 @@ "version": "2.18.1", "resolved": "https://registry.npmmirror.com/@antv/x6/-/x6-2.18.1.tgz", "integrity": "sha512-FkWdbLOpN9J7dfJ+kiBxzowSx2N6syBily13NMVdMs+wqC6Eo5sLXWCZjQHateTFWgFw7ZGi2y9o3Pmdov1sXw==", + "license": "MIT", "dependencies": { "@antv/x6-common": "^2.0.16", "@antv/x6-geometry": "^2.0.5", @@ -215,10 +225,77 @@ "integrity": "sha512-MId6riEQkxphBpVeTcL4ZNXL4lScyvDEPLyIafvWMcWNTGK0jgkK7N20XSzqt8ltJb0mGUso5s56mrk8ysHu2A==", "license": "MIT" }, + "node_modules/@antv/x6-plugin-clipboard": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@antv/x6-plugin-clipboard/-/x6-plugin-clipboard-2.1.6.tgz", + "integrity": "sha512-roZPLnZx6PK8MBvee0QMo90fz/TXeF0WNe4EGin2NBq5M1I5XTWrYvA6N2XVIiWAAI67gjQeEE8TpkL7f8QdqA==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-export": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@antv/x6-plugin-export/-/x6-plugin-export-2.1.6.tgz", + "integrity": "sha512-m0ukMmZhrFE5n7uCR43DVQBdiUfpjGN+vm1mc+6RTZdHK8pa6Mxr0RZztaxPy34YA4tli+bGY3ePslsNPfh6PQ==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-history": { + "version": "2.2.4", + "resolved": "https://registry.npmmirror.com/@antv/x6-plugin-history/-/x6-plugin-history-2.2.4.tgz", + "integrity": "sha512-9gHHvEW4Fla+1hxUV49zNgJyIMoV9CjVM52MrFgAJcvyRn1Kvxz4MfxiKlG+DEZUs+/zvfjl9pS6gJOd8laRkg==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-keyboard": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@antv/x6-plugin-keyboard/-/x6-plugin-keyboard-2.2.3.tgz", + "integrity": "sha512-pnCIC+mDyKKfkcDyLePfGxKVIqXBcldTgannITkHC1kc0IafRS1GMvzpvuDGrM5haRYd6Nwz8kjkJyHkJE4GPA==", + "license": "MIT", + "dependencies": { + "mousetrap": "^1.6.5" + }, + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-selection": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/@antv/x6-plugin-selection/-/x6-plugin-selection-2.2.2.tgz", + "integrity": "sha512-s2gtR9Onlhr7HOHqyqg0d+4sG76JCcQEbvrZZ64XmSChlvieIPlC3YtH4dg1KMNhYIuBmBmpSum6S0eVTEiPQw==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-snapline": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/@antv/x6-plugin-snapline/-/x6-plugin-snapline-2.1.7.tgz", + "integrity": "sha512-AsysoCb9vES0U2USNhEpYuO/W8I0aYfkhlbee5Kt4NYiMfQfZKQyqW/YjDVaS2pm38C1NKu1LdPVk/BBr4CasA==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-transform": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/@antv/x6-plugin-transform/-/x6-plugin-transform-2.1.8.tgz", + "integrity": "sha512-GvJuiJ4BKp0H7+qx3R1I+Vzbw5gXp9+oByXo/WyVxE3urOC7LC5sqnaDfIjyYMN6ROLPYPZraLSeSyYBgMgcDw==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, "node_modules/@antv/x6-react-shape": { "version": "2.2.3", "resolved": "https://registry.npmmirror.com/@antv/x6-react-shape/-/x6-react-shape-2.2.3.tgz", "integrity": "sha512-42PGxk3XLnx9bsHQiRPauai5UQCrsh0MbWI3MyHRpjQaC30FK1rqyjH/EJ8qH0p6/cAndszZzCaiPqBbs3FqLQ==", + "license": "MIT", "peerDependencies": { "@antv/x6": "^2.x", "react": ">=18.0.0", @@ -229,7 +306,6 @@ "version": "7.26.2", "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -291,7 +367,6 @@ "version": "7.26.2", "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.26.2.tgz", "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "dev": true, "dependencies": { "@babel/parser": "^7.26.2", "@babel/types": "^7.26.0", @@ -332,7 +407,6 @@ "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" @@ -371,7 +445,6 @@ "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -380,7 +453,6 @@ "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -411,7 +483,6 @@ "version": "7.26.2", "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz", "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", - "dev": true, "dependencies": { "@babel/types": "^7.26.0" }, @@ -467,7 +538,6 @@ "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.25.9.tgz", "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", @@ -481,7 +551,6 @@ "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.25.9.tgz", "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/generator": "^7.25.9", @@ -499,7 +568,6 @@ "version": "7.26.0", "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz", "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -516,16 +584,143 @@ "node": ">=10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmmirror.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.13.5", + "resolved": "https://registry.npmmirror.com/@emotion/cache/-/cache-11.13.5.tgz", + "integrity": "sha512-Z3xbtJ+UcK76eWkagZ1onvn/wAVb1GOMuR15s30Fm2wrMgC7jzpnO2JZXr4eujTTqoQFUrZIw/rT0c6Zzjca1g==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmmirror.com/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1672,6 +1867,12 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -2278,6 +2479,21 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -2369,7 +2585,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -2523,6 +2738,19 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -2538,6 +2766,22 @@ "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", "hasInstallScript": true }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/create-react-class": { "version": "15.7.0", "resolved": "https://registry.npmmirror.com/create-react-class/-/create-react-class-15.7.0.tgz", @@ -2655,7 +2899,6 @@ "version": "4.3.7", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -2682,6 +2925,15 @@ "node": ">=0.4.0" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2738,6 +2990,35 @@ "node": ">=10.13.0" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/es-module-lexer": { "version": "1.5.4", "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz", @@ -2794,7 +3075,6 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -3087,6 +3367,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", @@ -3258,6 +3544,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3337,7 +3632,6 @@ "version": "11.12.0", "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -3365,8 +3659,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "peer": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -3390,11 +3683,37 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hoist-non-react-statics": { "version": "2.5.5", "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -3404,6 +3723,20 @@ "node": ">= 4" } }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immer": { "version": "10.1.1", "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", @@ -3417,7 +3750,6 @@ "version": "3.3.0", "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3470,6 +3802,21 @@ "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3509,6 +3856,13 @@ "node": ">=8" } }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", @@ -3573,7 +3927,6 @@ "version": "3.0.2", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -3590,8 +3943,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "peer": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -3632,6 +3984,33 @@ "json-buffer": "3.0.1" } }, + "node_modules/less": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/less/-/less-4.2.1.tgz", + "integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", @@ -3645,6 +4024,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz", @@ -3720,11 +4105,43 @@ "yallist": "^3.0.2" } }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/medium-editor": { "version": "5.23.3", "resolved": "https://registry.npmmirror.com/medium-editor/-/medium-editor-5.23.3.tgz", "integrity": "sha512-he9/TdjX8f8MGdXGfCs8AllrYnqXJJvjNkDKmPg3aPW/uoIrlRqtkFthrwvmd+u4QyzEiadhCCM0EwTiRdUCJw==" }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3753,6 +4170,20 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -3859,8 +4290,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -3886,6 +4316,24 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", @@ -3965,7 +4413,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -3973,6 +4420,34 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", @@ -4000,11 +4475,16 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -4031,6 +4511,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/postcss": { "version": "8.4.49", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz", @@ -4097,6 +4588,14 @@ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -4818,6 +5317,26 @@ "node": ">=0.10.0" } }, + "node_modules/react-diff-viewer-continued": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/react-diff-viewer-continued/-/react-diff-viewer-continued-3.4.0.tgz", + "integrity": "sha512-kMZmUyb3Pv5L9vUtCfIGYsdOHs8mUojblGy1U1Sm0D7FhAOEsH9QhnngEIRo5hXWIPNGupNRJls1TJ6Eqx84eg==", + "license": "MIT", + "dependencies": { + "@emotion/css": "^11.11.2", + "classnames": "^2.3.2", + "diff": "^5.1.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", @@ -4934,11 +5453,27 @@ "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -5049,6 +5584,22 @@ ], "peer": true }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", @@ -5162,7 +5713,6 @@ "version": "0.6.1", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5232,6 +5782,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz", @@ -5669,6 +6231,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1dbe8129..5e80c87f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,13 @@ "@ant-design/icons": "^5.2.6", "@antv/layout": "^1.2.14-beta.8", "@antv/x6": "^2.18.1", + "@antv/x6-plugin-clipboard": "^2.1.6", + "@antv/x6-plugin-export": "^2.1.6", + "@antv/x6-plugin-history": "^2.2.4", + "@antv/x6-plugin-keyboard": "^2.2.3", + "@antv/x6-plugin-selection": "^2.2.2", + "@antv/x6-plugin-snapline": "^2.1.7", + "@antv/x6-plugin-transform": "^2.1.8", "@antv/x6-react-shape": "^2.2.3", "@logicflow/core": "^2.0.9", "@logicflow/extension": "^2.0.13", @@ -22,6 +29,7 @@ "dagre": "^0.8.5", "form-render": "^2.5.1", "react": "^18.2.0", + "react-diff-viewer-continued": "^3.4.0", "react-dom": "^18.2.0", "react-redux": "^9.0.4", "react-router-dom": "^6.21.0" @@ -37,6 +45,7 @@ "eslint": "^8.55.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "less": "^4.2.1", "typescript": "^5.3.3", "vite": "^5.0.8" } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3e2543ef..23836d1f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,6 +5,7 @@ import zhCN from 'antd/locale/zh_CN'; import router from './router'; const App: React.FC = () => { + console.log('App'); return ( diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.module.css b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.module.css deleted file mode 100644 index 982426e5..00000000 --- a/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.module.css +++ /dev/null @@ -1,32 +0,0 @@ -.flowDesigner { - display: flex; - gap: 16px; - height: 100%; -} - -.nodeTypes { - width: 200px; - flex-shrink: 0; -} - -.nodeType { - margin: 8px 0; - padding: 8px 12px; - border-radius: 4px; - cursor: move; - user-select: none; - transition: all 0.3s; -} - -.nodeType:hover { - opacity: 0.8; - transform: translateY(-1px); -} - -.canvas { - flex: 1; - border: 1px solid #e8e8e8; - border-radius: 4px; - background: #f5f5f5; - min-height: 600px; -} \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.tsx deleted file mode 100644 index 43b39b8b..00000000 --- a/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.tsx +++ /dev/null @@ -1,471 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Graph, Node, Edge, Shape, Cell } from '@antv/x6'; -import dagre from 'dagre'; -import { getNodeTypes } from '@/pages/Workflow/service'; -import type { NodeTypeDTO } from '@/pages/Workflow/types'; -import styles from './index.module.css'; -import { Card } from 'antd'; - -// 节点类型映射 -const NODE_TYPE_MAP: Record = { - 'TASK': 'SHELL', // 任务节点映射到 SHELL - 'START': 'START', - 'END': 'END' -}; - -interface FlowDesignerProps { - value?: string; - onChange?: (value: string) => void; - workflowInstanceId?: number; - readOnly?: boolean; -} - -const FlowDesigner: React.FC = ({ - value, - onChange, - workflowInstanceId, - readOnly = false -}) => { - const containerRef = useRef(null); - const graphRef = useRef(); - const [nodeTypes, setNodeTypes] = useState([]); - const registeredNodesRef = useRef(new Set()); - - // 注册节点类型 - const registerNodeTypes = (types: NodeTypeDTO[]) => { - types.forEach(nodeType => { - const nodeTypeCode = nodeType.code; - // 如果节点类型已经注册,则跳过 - if (registeredNodesRef.current.has(nodeTypeCode)) { - return; - } - - try { - // 根据节点类型注册不同的节点 - Graph.registerNode(nodeTypeCode, { - inherit: nodeType.category === 'GATEWAY' ? 'polygon' : - nodeType.category === 'BASIC' ? 'circle' : 'rect', - width: nodeType.category === 'BASIC' ? 60 : 120, - height: nodeType.category === 'BASIC' ? 60 : 60, - attrs: { - body: { - fill: nodeType.color || '#fff', - stroke: nodeType.color || '#1890ff', - strokeWidth: 2, - ...(nodeType.category === 'GATEWAY' ? { - refPoints: '0,10 10,0 20,10 10,20', - } : nodeType.category === 'TASK' ? { - rx: 4, - ry: 4, - } : {}), - }, - label: { - text: nodeType.name, - fill: nodeType.category === 'BASIC' ? '#fff' : '#000', - fontSize: 12, - fontWeight: nodeType.category === 'BASIC' ? 'bold' : 'normal', - }, - }, - ports: { - groups: { - in: { - position: 'left', - attrs: { - circle: { - r: 4, - magnet: true, - stroke: nodeType.color || '#1890ff', - strokeWidth: 2, - fill: '#fff', - }, - }, - }, - out: { - position: 'right', - attrs: { - circle: { - r: 4, - magnet: true, - stroke: nodeType.color || '#1890ff', - strokeWidth: 2, - fill: '#fff', - }, - }, - }, - }, - items: nodeTypeCode === 'START' ? [ - { id: 'out', group: 'out' } - ] : nodeTypeCode === 'END' ? [ - { id: 'in', group: 'in' } - ] : [ - { id: 'in', group: 'in' }, - { id: 'out', group: 'out' } - ], - }, - }); - - registeredNodesRef.current.add(nodeTypeCode); - console.log(`Node type ${nodeTypeCode} registered successfully`); - } catch (error) { - console.warn(`Node type ${nodeTypeCode} registration failed:`, error); - } - }); - }; - - // 加载节点类型 - useEffect(() => { - const loadNodeTypes = async () => { - try { - const response = await getNodeTypes(); - setNodeTypes(response); - } catch (error) { - console.error('加载节点类型失败:', error); - } - }; - loadNodeTypes(); - - // 清理函数 - return () => { - registeredNodesRef.current.clear(); - if (graphRef.current) { - graphRef.current.dispose(); - } - }; - }, []); - - // 初始化画布 - useEffect(() => { - if (!containerRef.current || !nodeTypes.length) return; - - // 注册节点类型 - registerNodeTypes(nodeTypes); - - // 创建画布 - const graph = new Graph({ - container: containerRef.current, - width: 800, - height: 600, - grid: { - size: 10, - visible: true, - type: 'doubleMesh', - args: [ - { - color: '#eee', - thickness: 1, - }, - { - color: '#ddd', - thickness: 1, - factor: 4, - }, - ], - }, - connecting: { - router: 'manhattan', - connector: { - name: 'rounded', - args: { radius: 8 }, - }, - anchor: 'center', - connectionPoint: 'anchor', - allowBlank: false, - snap: { radius: 20 }, - createEdge() { - return new Shape.Edge({ - attrs: { - line: { - stroke: '#1890ff', - strokeWidth: 2, - targetMarker: { - name: 'block', - width: 12, - height: 8, - }, - }, - }, - }); - }, - validateConnection({ sourceView, targetView, sourceMagnet, targetMagnet }) { - if (!sourceMagnet || !targetMagnet) return false; - - // 开始节点只能有出口连线 - if (sourceView?.cell.shape === 'START' && sourceMagnet.getAttribute('port-group') !== 'out') return false; - if (targetView?.cell.shape === 'START') return false; - - // 结束节点只能有入口连线 - if (sourceView?.cell.shape === 'END') return false; - if (targetView?.cell.shape === 'END' && targetMagnet.getAttribute('port-group') !== 'in') return false; - - // 普通节点的连线规则 - if (sourceMagnet.getAttribute('port-group') !== 'out') return false; - if (targetMagnet.getAttribute('port-group') !== 'in') return false; - - return sourceView !== targetView; - }, - }, - highlighting: { - magnetAvailable: { - name: 'stroke', - args: { - padding: 4, - attrs: { - strokeWidth: 4, - stroke: '#1890ff', - }, - }, - }, - }, - interacting: { - nodeMovable: !readOnly, - edgeMovable: !readOnly, - magnetConnectable: !readOnly, - }, - background: { - color: '#f5f5f5', - }, - }); - - // 添加拖拽事件处理 - const container = containerRef.current; - container.addEventListener('dragover', (e) => { - e.preventDefault(); - }); - - container.addEventListener('drop', (e) => { - e.preventDefault(); - const nodeTypeData = e.dataTransfer?.getData('nodeType'); - if (!nodeTypeData) return; - - try { - const nodeType = JSON.parse(nodeTypeData); - const { clientX, clientY } = e; - const { left, top } = container.getBoundingClientRect(); - const x = clientX - left; - const y = clientY - top; - - // 创建节点 - const node = graph.addNode({ - shape: nodeType.code, - x, - y, - data: { - type: nodeType.code - } - }); - - // 触发更新 - const updateEvent = new Event('node:moved'); - container.dispatchEvent(updateEvent); - } catch (error) { - console.error('创建节点失败:', error); - } - }); - - // 监听事件 - if (!readOnly) { - let updateTimer: number | null = null; - - const updateGraph = () => { - if (updateTimer) { - window.clearTimeout(updateTimer); - } - - updateTimer = window.setTimeout(() => { - const nodes = graph.getNodes().map(node => ({ - id: node.id, - type: node.data?.type || node.shape, - x: node.position().x, - y: node.position().y, - name: node.attr('label/text'), - })); - - const edges = graph.getEdges().map(edge => ({ - source: edge.getSourceCellId(), - target: edge.getTargetCellId(), - })); - - const data = { - nodes, - edges, - transitionConfig: JSON.stringify({ - transitions: edges.map(edge => ({ - from: edge.source, - to: edge.target - })) - }) - }; - - onChange?.(JSON.stringify(data)); - }, 300); - }; - - // 监听节点变化 - graph.on('node:moved', updateGraph); - graph.on('edge:connected', updateGraph); - graph.on('cell:removed', updateGraph); - - // 添加边的右键菜单删除功能 - graph.on('edge:contextmenu', ({ cell, e }) => { - e.preventDefault(); - cell.remove(); - }); - } - - // 加载流程数据 - if (value) { - try { - const graphData = JSON.parse(value); - console.log('原始数据:', graphData); - const cells: Cell[] = []; - - // 添加节点 - const nodesMap = new Map(); - if (graphData.nodes) { - console.log('处理节点数据:', graphData.nodes); - graphData.nodes.forEach((node: any) => { - // 将 TASK 类型映射到 SHELL - const nodeShape = NODE_TYPE_MAP[node.type] || node.type; - const cell = graph.createNode({ - id: node.id, // 保持原始 ID - shape: nodeShape, - x: node.x || 100, - y: node.y || 100, - label: node.name || node.type, - data: { - ...node, - type: node.type // 保存原始类型 - }, - }); - cells.push(cell); - nodesMap.set(node.id, cell); - }); - } - - // 处理边数据 - let edges: any[] = []; - if (graphData.edges) { - edges = graphData.edges; - } else if (graphData.transitionConfig) { - try { - const transitionConfig = typeof graphData.transitionConfig === 'string' - ? JSON.parse(graphData.transitionConfig) - : graphData.transitionConfig; - edges = transitionConfig.transitions.map((t: any) => ({ - source: t.from, - target: t.to - })); - } catch (error) { - console.error('解析 transitionConfig 失败:', error); - } - } - - // 添加边 - if (edges.length > 0) { - console.log('处理边数据:', edges); - edges.forEach((edge: any) => { - const sourceNode = nodesMap.get(edge.source); - const targetNode = nodesMap.get(edge.target); - console.log('连线:', edge.source, '->', edge.target, '节点:', sourceNode?.id, targetNode?.id); - - if (sourceNode && targetNode) { - const edgeCell = graph.createEdge({ - source: { cell: sourceNode.id, port: 'out' }, - target: { cell: targetNode.id, port: 'in' }, - router: { - name: 'manhattan', - args: { - padding: 20, - startDirections: ['right'], - endDirections: ['left'], - }, - }, - connector: { - name: 'rounded', - args: { radius: 8 }, - }, - attrs: { - line: { - stroke: '#1890ff', - strokeWidth: 2, - targetMarker: { - name: 'block', - width: 12, - height: 8, - }, - }, - }, - }); - cells.push(edgeCell); - } - }); - } - - // 重置画布并应用布局 - console.log('重置前的 cells:', cells); - console.log('重置前的 cells 内容:', cells.map(cell => ({ - id: cell.id, - isNode: cell.isNode(), - isEdge: cell.isEdge(), - source: cell.isEdge() ? cell.getSourceCellId() : undefined, - target: cell.isEdge() ? cell.getTargetCellId() : undefined - }))); - - graph.resetCells(cells); - console.log('重置后的节点:', graph.getNodes().map(node => ({ - id: node.id, - type: node.data?.type, - shape: node.shape - }))); - console.log('重置后的边:', graph.getEdges().map(edge => ({ - source: edge.getSourceCellId(), - target: edge.getTargetCellId() - }))); - graph.centerContent(); - } catch (error) { - console.error('加载流程数据失败:', error); - } - } - - graphRef.current = graph; - - return () => { - // 清理事件监听 - container.removeEventListener('dragover', (e) => e.preventDefault()); - container.removeEventListener('drop', (e) => e.preventDefault()); - graph.dispose(); - }; - }, [nodeTypes, value, onChange, readOnly]); - - return ( -
-
- - {nodeTypes.map(nodeType => ( -
{ - e.dataTransfer.setData('nodeType', JSON.stringify(nodeType)); - }} - > - {nodeType.name} -
- ))} -
-
-
-
- ); -}; - -export default FlowDesigner; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/FormDesigner/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/components/FormDesigner/index.tsx deleted file mode 100644 index c348e55a..00000000 --- a/frontend/src/pages/Workflow/Definition/Edit/components/FormDesigner/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useState } from 'react'; -import { Card } from 'antd'; -import FormRender, { useForm } from 'form-render'; - -interface FormDesignerProps { - value?: string; - onChange?: (value: string) => void; - readOnly?: boolean; -} - -const defaultSchema = { - type: 'object', - properties: { - input1: { - title: '输入框', - type: 'string' - } - } -}; - -const FormDesigner: React.FC = ({ - value, - onChange, - readOnly = false -}) => { - const [schema, setSchema] = useState(() => { - try { - return value ? JSON.parse(value) : defaultSchema; - } catch (error) { - return defaultSchema; - } - }); - - const form = useForm(); - - const handleSubmit = (formData: any) => { - console.log('Form data:', formData); - }; - - const handleFinishFailed = (errors: any) => { - console.log('Form validation errors:', errors); - }; - - return ( -
- - - -
- ); -}; - -export default FormDesigner; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/NodeConfig/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/components/NodeConfig/index.tsx deleted file mode 100644 index c3266b8c..00000000 --- a/frontend/src/pages/Workflow/Definition/Edit/components/NodeConfig/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React, { useEffect } from 'react'; -import { Form, Button, Space, Input } from 'antd'; -import type { FormInstance } from 'antd'; -// @ts-ignore -import FormRender from 'form-render'; -import type { NodeTypeDTO } from '@/pages/Workflow/types'; - -interface NodeConfigProps { - node: { - id: string; - type: string; - text?: { value: string; x: number; y: number }; - properties?: Record; - }; - nodeType?: NodeTypeDTO; - onSave: (config: any) => void; - onCancel: () => void; -} - -const NodeConfig: React.FC = ({ - node, - nodeType, - onSave, - onCancel -}) => { - const [form] = Form.useForm(); - - useEffect(() => { - if (node.properties) { - form.setFieldsValue({ - ...node.properties, - text: node.text?.value - }); - } - }, [node, form]); - - const handleFinish = (values: any) => { - onSave({ - ...values, - text: { value: values.text, x: node.text?.x, y: node.text?.y } - }); - }; - - const handleFinishFailed = (errors: any) => { - console.log('Form validation errors:', errors); - }; - - // 如果没有节点类型信息,显示基础配置 - if (!nodeType) { - return ( -
-
- - - - - - - - - - -
-
- ); - } - - // 解析配置模式 - const configSchema = nodeType.configSchema ? JSON.parse(nodeType.configSchema) : {}; - const defaultConfig = nodeType.defaultConfig ? JSON.parse(nodeType.defaultConfig) : {}; - - // 合并节点类型的配置模式 - const schema = { - type: 'object', - properties: { - text: { - title: '节点名称', - type: 'string', - required: true - }, - ...configSchema.properties - }, - required: ['text', ...(configSchema.required || [])] - }; - - return ( -
- -
- - - - -
-
- ); -}; - -export default NodeConfig; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/index.tsx deleted file mode 100644 index 5b522a59..00000000 --- a/frontend/src/pages/Workflow/Definition/Edit/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Card, Tabs, Button, Form, Input, message } from 'antd'; -import type { TabsProps } from 'antd'; -import { useNavigate, useParams } from 'react-router-dom'; -import FlowDesigner from './components/FlowDesigner'; -import FormDesigner from './components/FormDesigner'; -import { getDefinition, createDefinition, updateDefinition } from '../../service'; -import type { WorkflowDefinitionRequest, WorkflowStatus } from '../../types'; - -const Edit: React.FC = () => { - const navigate = useNavigate(); - const { id } = useParams<{ id: string }>(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [graphData, setGraphData] = useState(''); - const [formData, setFormData] = useState(''); - - useEffect(() => { - if (id) { - setLoading(true); - getDefinition(parseInt(id)) - .then(data => { - form.setFieldsValue({ - name: data.name, - code: data.code, - description: data.description - }); - setGraphData(data.graphDefinition || ''); - setFormData(data.formDefinition || ''); - }) - .finally(() => { - setLoading(false); - }); - } - }, [id, form]); - - const handleSave = async () => { - try { - const values = await form.validateFields(); - const data: WorkflowDefinitionRequest = { - ...values, - graphDefinition: graphData, - formDefinition: formData, - nodeConfig: '{}', - transitionConfig: '{}', - enabled: true, - version: 1, - status: 'DRAFT' as WorkflowStatus - }; - - if (id) { - await updateDefinition(parseInt(id), data); - } else { - await createDefinition(data); - } - - message.success('保存成功'); - navigate('/workflow/definition'); - } catch (error) { - console.error('Save failed:', error); - message.error('保存失败'); - } - }; - - const items: TabsProps['items'] = [ - { - key: 'basic', - label: '基本信息', - children: ( -
- - - - - - - - - -
- ) - }, - { - key: 'flow', - label: '流程设计', - children: ( - - ) - }, - { - key: 'form', - label: '表单设计', - children: ( - - ) - } - ]; - - return ( - - 保存 - - } - > - - - ); -}; - -export default Edit; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/index.tsx b/frontend/src/pages/Workflow/Definition/index.tsx deleted file mode 100644 index 41b9e0ba..00000000 --- a/frontend/src/pages/Workflow/Definition/index.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Card, Table, Button, Space, Tag, Popconfirm, message } from 'antd'; -import { useNavigate } from 'react-router-dom'; -import type { ColumnsType } from 'antd/es/table'; -import { getDefinitions, deleteDefinition, publishDefinition, disableDefinition, enableDefinition } from '../service'; -import { WorkflowDefinitionResponse, WorkflowStatus } from '../types'; - -const Definition: React.FC = () => { - const navigate = useNavigate(); - const [loading, setLoading] = useState(false); - const [list, setList] = useState([]); - - const loadData = async () => { - try { - setLoading(true); - const response = await getDefinitions(); - if (response) { - setList(response.content); - } - } catch (error) { - message.error('加载数据失败'); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - loadData(); - }, []); - - const handlePublish = async (id: number) => { - try { - await publishDefinition(id); - message.success('发布成功'); - loadData(); - } catch (error) { - message.error('发布失败'); - } - }; - - const handleDisable = async (id: number) => { - try { - await disableDefinition(id); - message.success('禁用成功'); - loadData(); - } catch (error) { - message.error('禁用失败'); - } - }; - - const handleEnable = async (id: number) => { - try { - await enableDefinition(id); - message.success('启用成功'); - loadData(); - } catch (error) { - message.error('启用失败'); - } - }; - - const handleDelete = async (id: number) => { - try { - await deleteDefinition(id); - message.success('删除成功'); - loadData(); - } catch (error) { - message.error('删除失败'); - } - }; - - const columns: ColumnsType = [ - { - title: '流程名称', - dataIndex: 'name', - width: 200, - }, - { - title: '流程编码', - dataIndex: 'code', - width: 200, - }, - { - title: '状态', - dataIndex: 'status', - width: 120, - render: (status: WorkflowStatus) => { - const statusMap = { - 'DRAFT': { text: '草稿', color: 'default' }, - 'PUBLISHED': { text: '已发布', color: 'success' }, - 'DISABLED': { text: '已禁用', color: 'error' }, - }; - const { text, color } = statusMap[status]; - return {text}; - }, - }, - { - title: '描述', - dataIndex: 'description', - ellipsis: true, - }, - { - title: '创建时间', - dataIndex: 'createTime', - width: 180, - }, - { - title: '操作', - width: 280, - render: (_, record) => ( - - - {record.status === 'DRAFT' && ( - - )} - {record.status === 'PUBLISHED' && ( - - )} - {record.status === 'DISABLED' && ( - - )} - handleDelete(record.id)} - > - - - - ), - }, - ]; - - return ( - navigate('/workflow/definition/edit')} - > - 新建流程 - - } - > - - - ); -}; - -export default Definition; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Instance/index.tsx b/frontend/src/pages/Workflow/Instance/index.tsx deleted file mode 100644 index a68ab2f5..00000000 --- a/frontend/src/pages/Workflow/Instance/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { Card } from 'antd'; - -const WorkflowInstance: React.FC = () => { - return ( - -
工作流实例管理(开发中)
-
- ); -}; - -export default WorkflowInstance; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Monitor/index.tsx b/frontend/src/pages/Workflow/Monitor/index.tsx deleted file mode 100644 index 3f301aaf..00000000 --- a/frontend/src/pages/Workflow/Monitor/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { Card } from 'antd'; - -const WorkflowMonitor: React.FC = () => { - return ( - -
工作流监控大盘(开发中)
-
- ); -}; - -export default WorkflowMonitor; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/service.ts b/frontend/src/pages/Workflow/service.ts deleted file mode 100644 index 2f5a90f0..00000000 --- a/frontend/src/pages/Workflow/service.ts +++ /dev/null @@ -1,104 +0,0 @@ -import request from '@/utils/request'; -import type { Page } from '@/types/base/page'; -import type { BaseResponse } from '@/types/base/response'; -import type { - WorkflowDefinitionResponse, - WorkflowDefinitionRequest, - WorkflowDefinitionQuery, - WorkflowInstanceResponse, - WorkflowInstanceQuery, - NodeInstanceResponse, - WorkflowLogResponse, - WorkflowDefinition, - NodeTypeDTO -} from './types'; - -const DEFINITION_URL = '/api/v1/workflow-definitions'; -const INSTANCE_URL = '/api/v1/workflow-instance'; -const NODE_URL = '/api/v1/node-instance'; -const LOG_URL = '/api/v1/workflow-logs'; -const NODE_TYPE_URL = '/api/v1/node-types'; - -// 工作流定义相关接口 -export const getDefinitions = (params?: WorkflowDefinitionQuery) => - request.get>(`${DEFINITION_URL}/page`, { params }); - -export const getDefinition = (id: number) => - request.get(`${DEFINITION_URL}/${id}`); - -export const createDefinition = (data: WorkflowDefinitionRequest) => - request.post(DEFINITION_URL, data); - -export const updateDefinition = (id: number, data: WorkflowDefinitionRequest) => - request.put(`${DEFINITION_URL}/${id}`, data); - -export const deleteDefinition = (id: number) => - request.delete(`${DEFINITION_URL}/${id}`); - -export const publishDefinition = (id: number) => - request.post(`${DEFINITION_URL}/${id}/publish`); - -export const disableDefinition = (id: number) => - request.post(`${DEFINITION_URL}/${id}/disable`); - -export const enableDefinition = (id: number) => - request.post(`${DEFINITION_URL}/${id}/enable`); - -// 工作流实例相关接口 -export const getInstances = (params?: WorkflowInstanceQuery) => - request.get>(`${INSTANCE_URL}`, { params }); - -export const getInstance = (id: number) => - request.get(`${INSTANCE_URL}/${id}`); - -export const createInstance = (definitionId: number, businessKey: string, variables?: Record) => - request.post(INSTANCE_URL, { definitionId, businessKey, variables }); - -export const startInstance = (id: number) => - request.post(`${INSTANCE_URL}/${id}/start`); - -export const cancelInstance = (id: number) => - request.post(`${INSTANCE_URL}/${id}/cancel`); - -export const pauseInstance = (id: number) => - request.post(`${INSTANCE_URL}/${id}/pause`); - -export const resumeInstance = (id: number) => - request.post(`${INSTANCE_URL}/${id}/resume`); - -// 节点实例相关接口 -export const getNodeInstances = (workflowInstanceId: number) => - request.get(`${NODE_URL}/workflow/${workflowInstanceId}`); - -export const getNodeInstancesByStatus = (workflowInstanceId: number, status: string) => - request.get(`${NODE_URL}/workflow/${workflowInstanceId}/status/${status}`); - -export const updateNodeStatus = (id: number, status: string) => - request.put(`${NODE_URL}/${id}/status`, { status }); - -// 日志相关接口 -export const getWorkflowLogs = (workflowInstanceId: number) => - request.get(`${LOG_URL}/workflow/${workflowInstanceId}`); - -export const getNodeLogs = (workflowInstanceId: number, nodeId: string) => - request.get(`${LOG_URL}/node/${workflowInstanceId}/${nodeId}`); - -export const recordLog = (data: Omit) => - request.post(`${LOG_URL}/record`, data); - -// 获取工作流定义列表 -export const getDefinitionList = (params: WorkflowDefinitionQuery) => - request.get('/api/v1/workflow/definition', { params }); - -// 保存工作流定义 -export const saveDefinition = (data: WorkflowDefinition) => { - if (data.id) { - return request.put(`/api/v1/workflow/definition/${data.id}`, data); - } - return request.post('/api/v1/workflow/definition', data); -}; - -// 获取节点类型列表 -export const getNodeTypes = () => - request.get(NODE_TYPE_URL); - \ No newline at end of file diff --git a/frontend/src/pages/Workflow/types.ts b/frontend/src/pages/Workflow/types.ts deleted file mode 100644 index eb199677..00000000 --- a/frontend/src/pages/Workflow/types.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { BaseResponse } from '@/types/base/response'; -import { BaseQuery } from '@/types/base/query'; - -// 工作流状态枚举 -export enum WorkflowStatus { - DRAFT = 'DRAFT', - PUBLISHED = 'PUBLISHED', - DISABLED = 'DISABLED' -} - -// 节点类型枚举 -export enum NodeType { - START = 'START', - END = 'END', - TASK = 'TASK', - GATEWAY = 'GATEWAY', - SUB_PROCESS = 'SUB_PROCESS', - SHELL = 'SHELL' -} - -// 节点状态枚举 -export enum NodeStatus { - PENDING = 'PENDING', - RUNNING = 'RUNNING', - COMPLETED = 'COMPLETED', - FAILED = 'FAILED', - CANCELLED = 'CANCELLED', - PAUSED = 'PAUSED', - SKIPPED = 'SKIPPED' -} - -// 工作流实例状态枚举 -export enum InstanceStatus { - RUNNING = 'RUNNING', - COMPLETED = 'COMPLETED', - FAILED = 'FAILED', - CANCELLED = 'CANCELLED', - PAUSED = 'PAUSED' -} - -// 日志级别枚举 -export enum LogLevel { - INFO = 'INFO', - WARN = 'WARN', - ERROR = 'ERROR' -} - -// 工作流定义基础接口 -export interface WorkflowDefinition { - id?: number; - name: string; - code: string; - description?: string; - nodeConfig: string; - transitionConfig: string; - formDefinition: string; - graphDefinition: string; - enabled: boolean; - version: number; - status: WorkflowStatus; - remark?: string; - createTime?: string; - updateTime?: string; -} - -// 工作流定义响应数据 -export interface WorkflowDefinitionResponse extends BaseResponse, WorkflowDefinition { - id: number; - createTime: string; - updateTime: string; -} - -// 工作流定义查询参数 -export interface WorkflowDefinitionQuery extends BaseQuery { - name?: string; - code?: string; - status?: WorkflowStatus; -} - -// 工作流定义请求数据 -export interface WorkflowDefinitionRequest extends Omit { - id?: number; -} - -// 节点定义响应数据 -export interface NodeDefinitionResponse extends BaseResponse { - nodeId: string; - name: string; - type: NodeType; - config: string; - description?: string; - workflowDefinitionId: number; - orderNum: number; -} - -// 工作流实例查询参数 -export interface WorkflowInstanceQuery extends BaseQuery { - definitionId?: number; - status?: InstanceStatus; - startTime?: string; - endTime?: string; -} - -// 工作流实例响应数据 -export interface WorkflowInstanceResponse extends BaseResponse { - definitionId: number; - businessKey: string; - status: InstanceStatus; - startTime: string; - endTime?: string; - variables: string; - error?: string; -} - -// 节点实例响应数据 -export interface NodeInstanceResponse extends BaseResponse { - workflowInstanceId: number; - nodeId: string; - nodeType: NodeType; - name: string; - status: NodeStatus; - startTime: string; - endTime?: string; - config: string; - input: string; - output: string; - error?: string; - preNodeId: string; -} - -// 工作流日志响应数据 -export interface WorkflowLogResponse extends BaseResponse { - workflowInstanceId: number; - nodeId: string; - message: string; - level: LogLevel; - detail?: string; -} - -// 节点类型数据结构 -export interface NodeTypeDTO extends BaseResponse { - code: string; // 节点类型编码 - name: string; // 节点类型名称 - description: string; // 节点类型描述 - category: 'BASIC' | 'TASK' | 'GATEWAY' | 'EVENT'; // 节点类型分类 - icon: string; // 节点图标 - color: string; // 节点颜色 - executors?: Array<{ // 支持的执行器列表 - code: string; // 执行器编码 - name: string; // 执行器名称 - description: string; // 执行器描述 - configSchema: any; // 执行器配置模式 - }>; - configSchema?: any; // 节点配置模式 - defaultConfig?: any; // 默认配置 - enabled: boolean; // 是否启用 - deleted: boolean; // 是否删除 - version: number; // 版本号 - createBy: string; // 创建人 - updateBy: string; // 更新人 -} \ No newline at end of file diff --git a/frontend/src/pages/flow-designer/components/FlowGraph.tsx b/frontend/src/pages/flow-designer/components/FlowGraph.tsx deleted file mode 100644 index 4fdd57e2..00000000 --- a/frontend/src/pages/flow-designer/components/FlowGraph.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Graph } from '@antv/x6'; -import { register } from '@antv/x6-react-shape'; -import { Selection } from '@antv/x6-plugin-selection'; -import { Keyboard } from '@antv/x6-plugin-keyboard'; -import { Snapline } from '@antv/x6-plugin-snapline'; -import { Transform } from '@antv/x6-plugin-transform'; -import { History } from '@antv/x6-plugin-history'; -import { Export } from '@antv/x6-plugin-export'; - -// 注册自定义节点 -register({ - shape: 'custom-node', - width: 100, - height: 40, - component: ({ node }) => { - const data = node.getData() || {}; - return ( -
- {data.label || 'Node'} -
- ); - }, -}); - -interface FlowGraphProps { - onNodeSelected?: (nodeId: string) => void; - onEdgeSelected?: (edgeId: string) => void; -} - -const FlowGraph: React.FC = ({ onNodeSelected, onEdgeSelected }) => { - const containerRef = useRef(null); - const graphRef = useRef(); - - useEffect(() => { - if (!containerRef.current) return; - - const graph = new Graph({ - container: containerRef.current, - width: 800, - height: 600, - grid: { - size: 10, - visible: true, - type: 'dot', - args: { - color: '#ccc', - thickness: 1, - }, - }, - connecting: { - snap: true, - allowBlank: false, - allowLoop: false, - highlight: true, - connector: 'smooth', - connectionPoint: 'boundary', - createEdge() { - return this.createEdge({ - shape: 'edge', - attrs: { - line: { - stroke: '#5F95FF', - strokeWidth: 1, - targetMarker: { - name: 'classic', - size: 8, - }, - }, - }, - router: { - name: 'manhattan', - }, - }); - }, - }, - highlighting: { - magnetAvailable: { - name: 'stroke', - args: { - padding: 4, - attrs: { - strokeWidth: 4, - stroke: '#52c41a', - }, - }, - }, - }, - mousewheel: { - enabled: true, - modifiers: ['ctrl', 'meta'], - }, - interacting: { - magnetConnectable: true, - nodeMovable: true, - }, - }); - - // 注册插件 - graph.use( - new Selection({ - multiple: true, - rubberband: true, - movable: true, - showNodeSelectionBox: true, - }) - ); - graph.use( - new Keyboard({ - enabled: true, - }) - ); - graph.use(new Snapline()); - graph.use(new Transform()); - graph.use(new History()); - graph.use(new Export()); - - // 监听事件 - graph.on('node:click', ({ node }) => { - onNodeSelected?.(node.id); - }); - - graph.on('edge:click', ({ edge }) => { - onEdgeSelected?.(edge.id); - }); - - // 快捷键 - graph.bindKey(['meta+c', 'ctrl+c'], () => { - const cells = graph.getSelectedCells(); - if (cells.length) { - graph.copy(cells); - } - return false; - }); - - graph.bindKey(['meta+v', 'ctrl+v'], () => { - if (!graph.isClipboardEmpty()) { - const cells = graph.paste({ offset: 32 }); - graph.cleanSelection(); - graph.select(cells); - } - return false; - }); - - graph.bindKey(['meta+z', 'ctrl+z'], () => { - if (graph.canUndo()) { - graph.undo(); - } - return false; - }); - - graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => { - if (graph.canRedo()) { - graph.redo(); - } - return false; - }); - - graph.bindKey('delete', () => { - const cells = graph.getSelectedCells(); - if (cells.length) { - graph.removeCells(cells); - } - }); - - graphRef.current = graph; - - // 添加示例节点 - graph.addNode({ - shape: 'custom-node', - x: 100, - y: 100, - data: { label: '开始' }, - }); - - graph.addNode({ - shape: 'custom-node', - x: 300, - y: 100, - data: { label: '处理' }, - }); - - graph.addNode({ - shape: 'custom-node', - x: 500, - y: 100, - data: { label: '结束' }, - }); - - return () => { - graph.dispose(); - }; - }, [onNodeSelected, onEdgeSelected]); - - return ( -
- ); -}; - -export default FlowGraph; \ No newline at end of file diff --git a/frontend/src/pages/flow-designer/components/NodeContextMenu.tsx b/frontend/src/pages/flow-designer/components/NodeContextMenu.tsx deleted file mode 100644 index 663a9889..00000000 --- a/frontend/src/pages/flow-designer/components/NodeContextMenu.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { Menu } from 'antd'; -import type { MenuProps } from 'antd'; -import { DeleteOutlined, EditOutlined, CopyOutlined } from '@ant-design/icons'; - -export const NodeContextMenu = () => { - return ( - - }> - Delete - - }> - Edit - - }> - Copy - - - ); -}; \ No newline at end of file diff --git a/frontend/src/pages/flow-designer/index.tsx b/frontend/src/pages/flow-designer/index.tsx deleted file mode 100644 index 61a97840..00000000 --- a/frontend/src/pages/flow-designer/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useState } from 'react'; -import { Card, Space, Button, Drawer, Form, Input, Select } from 'antd'; -import FlowGraph from './components/FlowGraph'; - -const FlowDesigner: React.FC = () => { - const [selectedNode, setSelectedNode] = useState(null); - const [drawerVisible, setDrawerVisible] = useState(false); - const [form] = Form.useForm(); - - const handleNodeSelected = (nodeId: string) => { - setSelectedNode(nodeId); - setDrawerVisible(true); - }; - - const handleSave = async () => { - try { - const values = await form.validateFields(); - console.log('保存节点属性:', values); - setDrawerVisible(false); - } catch (error) { - console.error('表单验证失败:', error); - } - }; - - return ( -
- - - - - - } - > - console.log('选中连线:', edgeId)} - /> - - - setDrawerVisible(false)} - open={drawerVisible} - width={400} - extra={ - - - - - } - > -
- - - - -