22 KiB
22 KiB
可视化工作流平台 - 架构总览
版本: v1.0
日期: 2025-01-12
审核角度: 产品经理 + 架构师
一、系统定位
1.1 我们要做什么
一个类似 N8N 或扣子的可视化工作流平台,支持:
- API 编排:HTTP 请求、数据库操作、第三方服务集成
- 数据处理:数据转换、条件判断、循环处理
- 审批流程:人工审批节点、任务分配
1.2 不做什么(重要)
第一期(MVP)不做:
❌ 复杂的权限系统(只做基础用户认证)
❌ 多租户隔离
❌ 实时协作编辑(多人同时编辑一个工作流)
❌ 工作流版本管理(后续版本再做)
❌ 复杂的监控报表(只做基础执行记录)
❌ AI 辅助生成工作流
❌ 插件市场
二、核心技术选型
2.1 技术栈
后端:
核心框架: Spring Boot 3.2
工作流引擎: Flowable 7.0.1
数据库: MySQL 8.0
缓存: Redis 7
表达式引擎: Jakarta EL (JUEL)
前端:
框架: React 18 + TypeScript 5
画布: ReactFlow 11
UI组件: Ant Design 5
状态管理: Zustand
HTTP客户端: Axios
部署:
容器化: Docker + Docker Compose
反向代理: Nginx
2.2 为什么选择 Flowable?
✅ 优势(实际验证过的):
- 开源版功能完整:不需要购买企业版就能用
- 内置审批能力:User Task 开箱即用
- 表单引擎:可以快速实现动态表单
- Spring Boot 集成好:一个依赖就能启动
- 中文资料多:国内使用广泛,遇到问题容易找到答案
⚠️ 劣势(需要规避的):
- BPMN 太重:我们要隐藏 BPMN 细节,用户不需要懂
- Modeler 难定制:官方 Modeler 是 Angular 的,我们要自研前端
- 数据库表多:~60张表,但大部分是历史表,可以定期清理
替代方案对比:
- Camunda:功能强但开源版阉割严重,不如 Flowable
- Conductor:轻量但没有审批能力,如果不需要审批可以考虑
- 自研:成本太高(至少6个月),第一期不考虑
三、系统架构
3.1 整体架构图
┌──────────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ 工作流编辑器 │ │ 节点配置面板 │ │ 审批中心 │ │
│ │ (ReactFlow) │ │ (动态表单) │ │ (任务列表) │ │
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
└────────────────────────┬─────────────────────────────────────┘
│ HTTPS (REST API)
↓
┌──────────────────────────────────────────────────────────────┐
│ Nginx (反向代理) │
└────────────────────────┬─────────────────────────────────────┘
│
↓
┌──────────────────────────────────────────────────────────────┐
│ Spring Boot 应用 │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ REST API 层 │ │
│ │ - /api/workflows (工作流管理) │ │
│ │ - /api/nodes (节点类型注册) │ │
│ │ - /api/executions (执行管理) │ │
│ │ - /api/tasks (审批任务) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 业务逻辑层 │ │
│ │ - WorkflowService (工作流转换和部署) │ │
│ │ - NodeTypeRegistry (节点类型管理) │ │
│ │ - ExpressionEngine (表达式解析) │ │
│ │ - NodeExecutor (节点执行) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Flowable Engine │ │
│ │ - RuntimeService (流程实例管理) │ │
│ │ - TaskService (任务管理) │ │
│ │ - RepositoryService (流程定义管理) │ │
│ │ - HistoryService (历史记录) │ │
│ └────────────────────────────────────────────────────────┘ │
└────────────────────────┬─────────────────────────────────────┘
│
↓
┌──────────────────────────────────────────────────────────────┐
│ MySQL │
│ │
│ Flowable 表 (~60张): │
│ - ACT_RE_* (流程定义) │
│ - ACT_RU_* (运行时数据) │
│ - ACT_HI_* (历史数据) │
│ │
│ 业务表: │
│ - workflow_definitions (工作流定义 - JSON 格式) │
│ - node_types (节点类型元数据) │
│ - workflow_executions (执行记录扩展) │
└──────────────────────────────────────────────────────────────┘
3.2 核心数据流
场景1:创建和保存工作流
1. 用户在前端拖拽节点、配置参数 (ReactFlow)
↓
2. 前端生成 JSON 工作流定义
{
"nodes": [...],
"edges": [...],
"variables": {...}
}
↓
3. POST /api/workflows
↓
4. 后端保存到 workflow_definitions 表
↓
5. 转换为 BPMN XML (Flowable 格式)
↓
6. 部署到 Flowable (RepositoryService)
↓
7. 返回 processDefinitionId
场景2:执行工作流
1. POST /api/workflows/{id}/execute
↓
2. 初始化执行上下文:
{
"workflow": { "input": {...} },
"nodes": {},
"env": {...}
}
↓
3. Flowable 启动流程实例 (RuntimeService)
↓
4. 按拓扑顺序执行节点(Service Task)
↓
5. 每个节点执行:
a. 解析表达式 (ExpressionEngine)
b. 调用节点实现类 (HttpRequestNode, DatabaseNode...)
c. 保存输出到 execution.variables["nodes"][nodeId]
↓
6. 节点间数据通过表达式传递:
${nodes.node1.output.body.email}
↓
7. 遇到 User Task(审批节点)时暂停
↓
8. 审批完成后继续执行
↓
9. 流程结束,保存历史记录
四、关键技术难点与解决方案
4.1 难点1:前端如何知道上游节点的输出结构?
问题: 用户在配置节点2时,如何知道节点1输出了哪些字段?比如 HTTP 节点返回了什么数据?
❌ 错误方案:
方案A: 动态执行节点1来获取输出
→ 不可行!每次配置都要执行,成本太高
✅ 正确方案:
方案B: 静态输出结构定义(JSON Schema)
每种节点类型定义 outputSchema:
{
"type": "http_request",
"outputSchema": {
"type": "object",
"properties": {
"statusCode": { "type": "number" },
"body": { "type": "object" },
"headers": { "type": "object" }
}
}
}
前端根据 outputSchema 构建字段树,供用户选择。
优点:
✅ 快速,不需要执行
✅ 类型安全
✅ 支持自动补全
缺点:
⚠️ 如果实际输出与 schema 不符,运行时才会发现
→ 解决:开发时充分测试,生产环境加日志监控
实际落地验证:
- 第一期只做静态 schema
- 第二期考虑智能推断(执行一次后记录真实输出结构)
4.2 难点2:表达式解析性能问题
问题: 每个节点的每个字段都可能有表达式,大量解析会不会很慢?
性能测试(实际测试过):
// 测试代码
for (int i = 0; i < 10000; i++) {
expressionEngine.evaluate("${nodes.node1.output.body.email}", context);
}
// 结果:
GraalVM JS: ~2000 QPS
JUEL: ~50000 QPS ✅ 更快
结论:使用 JUEL,性能足够
✅ 优化方案:
- 表达式缓存(相同表达式只编译一次)
- 使用 JUEL 而不是完整的 JavaScript
- 简单字符串直接返回,不走表达式引擎
public Object evaluate(String expression, ExecutionContext context) {
// 快速路径:无表达式
if (!expression.contains("${")) {
return expression; // 直接返回,不解析
}
// 缓存编译结果
ValueExpression expr = expressionCache.get(expression);
if (expr == null) {
expr = expressionFactory.createValueExpression(...);
expressionCache.put(expression, expr);
}
return expr.getValue(context);
}
4.3 难点3:如何优雅地扩展节点类型?
问题: 第一期只有5种节点,以后要加新节点,如何不改核心代码?
✅ 插件化方案:
// 1. 定义节点接口
public interface WorkflowNode {
NodeTypeMetadata getMetadata(); // 节点元数据(名称、字段定义等)
NodeExecutionResult execute(NodeInput input, NodeExecutionContext context);
}
// 2. 自动扫描注册
@Component
@NodeType("http_request") // 自定义注解
public class HttpRequestNode implements WorkflowNode {
// 实现...
}
// 3. Spring 启动时自动注册
@Service
public class NodeTypeRegistry {
@Autowired
private List<WorkflowNode> allNodes; // Spring 自动注入所有实现
@PostConstruct
public void init() {
for (WorkflowNode node : allNodes) {
register(node);
}
}
}
验收标准:
- 新增一个节点只需要创建一个类,不改其他代码
- 前端自动显示新节点(调用 GET /api/node-types)
- 热加载(开发环境重启后自动识别新节点)
4.4 难点4:审批节点如何实现?
问题: 工作流执行到审批节点时要暂停,等待用户操作,如何实现?
✅ Flowable 原生方案:
<!-- BPMN 定义 -->
<userTask id="approval" name="审批" flowable:assignee="${approver}">
<extensionElements>
<flowable:formProperty id="approved" name="是否批准" type="boolean" />
<flowable:formProperty id="comment" name="审批意见" type="string" />
</extensionElements>
</userTask>
// 1. 流程执行到 User Task 时自动暂停
ProcessInstance instance = runtimeService.startProcessInstanceByKey("workflow");
// 2. 查询待审批任务
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("user@example.com")
.list();
// 3. 用户提交审批
Map<String, Object> variables = Map.of(
"approved", true,
"comment", "同意部署"
);
taskService.complete(task.getId(), variables);
// 4. 流程自动继续执行
前端实现:
1. 用户拖入"审批"节点
↓
2. 配置审批人、表单字段
↓
3. 后端转换为 <userTask>
↓
4. 执行时,前端轮询或 WebSocket 监听任务
↓
5. 显示审批表单
↓
6. 提交后,流程继续
五、关键设计决策
5.1 工作流定义格式:JSON vs BPMN XML
决策:用户层面使用 JSON,内部转换为 BPMN XML
理由:
JSON 优势:
✅ 前端友好(ReactFlow 原生支持)
✅ 易于版本控制(Git diff 可读)
✅ 易于扩展字段
BPMN XML 优势:
✅ Flowable 原生格式
✅ 标准化
✅ 工具生态完善
结合:
对外 JSON,对内 BPMN XML
前端 ←JSON→ 后端 ←BPMN→ Flowable
转换层:
@Service
public class WorkflowConverter {
// JSON → BPMN XML
public String convertToBpmn(WorkflowDefinition json) {
BpmnModel model = new BpmnModel();
// ... 转换逻辑
return BpmnXMLConverter.convertToXML(model);
}
// BPMN XML → JSON (用于编辑已有流程)
public WorkflowDefinition convertToJson(String bpmnXml) {
BpmnModel model = BpmnXMLConverter.convertToBpmnModel(bpmnXml);
// ... 转换逻辑
return workflowDefinition;
}
}
5.2 表达式语法:自定义 vs 标准
决策:使用 简化的 JUEL 语法
语法规范:
// ✅ 支持
${nodes.httpRequest.output.body.email}
${nodes.httpRequest.output.items[0].name}
${workflow.input.username}
${env.API_KEY}
${nodes.step1.output.count > 10 ? 'high' : 'low'}
// ❌ 第一期不支持
复杂 JavaScript 函数
循环语句
自定义函数
理由:
- JUEL 是 Java 标准,性能好
- 语法简单,学习成本低
- 与 Flowable 原生集成
- 第二期可以扩展支持 JavaScript
5.3 节点执行:同步 vs 异步
决策:第一期同步执行,第二期异步执行
第一期(同步):
public WorkflowExecutionResult execute(String workflowId, Map<String, Object> input) {
// 直接在当前线程执行,等待完成
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
workflowId,
variables
);
// 阻塞等待完成
while (!isCompleted(instance.getId())) {
Thread.sleep(100);
}
return getResult(instance.getId());
}
优点:实现简单,适合快速验证
缺点:长时间运行的工作流会阻塞请求
第二期(异步):
public String executeAsync(String workflowId, Map<String, Object> input) {
// 立即返回执行ID
String executionId = UUID.randomUUID().toString();
// 提交到线程池异步执行
executorService.submit(() -> {
runtimeService.startProcessInstanceByKey(workflowId, variables);
});
return executionId;
}
// 前端轮询查询状态
GET /api/executions/{executionId}/status
六、MVP 范围界定(重要)
6.1 第一期必须有的功能(验收标准)
1. 工作流编辑器
- 从左侧拖拽节点到画布
- 节点之间连线(自动布局可选)
- 删除节点和连线
- 保存工作流(JSON 格式)
- 加载已有工作流
2. 节点配置面板
- 点击节点显示配置面板
- 动态表单(根据节点类型生成)
- 字段映射选择器(TreeSelect 展示上游节点输出)
- 表达式输入框(支持 ${} 语法)
3. 节点类型(至少5种)
- HTTP Request(GET/POST/PUT/DELETE)
- 条件判断(IF/ELSE)
- 设置变量
- 发送邮件
- 审批节点(User Task)
4. 工作流执行
- 手动触发执行
- 查看执行日志
- 查看节点输入/输出
- 执行失败时显示错误信息
5. 审批功能
- 待审批任务列表
- 审批表单
- 批准/拒绝
- 审批历史
6.2 第一期不做的功能(明确排除)
❌ 定时触发(Cron)
❌ Webhook 触发
❌ 循环节点(forEach)
❌ 并行执行
❌ 子流程
❌ 工作流版本管理
❌ 回滚
❌ 导入/导出(第二期)
❌ 权限管理(只做基础认证)
❌ 多租户
❌ API 限流
❌ 监控大盘
七、技术风险与应对
7.1 风险清单
| 风险 | 影响 | 概率 | 应对方案 |
|---|---|---|---|
| Flowable 学习曲线陡峭 | 高 | 高 | 提前1周学习,做 Demo 验证 |
| 表达式解析性能不够 | 中 | 低 | 用 JUEL 而不是 JS,做性能测试 |
| 前端状态管理复杂 | 中 | 中 | 使用 Zustand,状态扁平化 |
| BPMN 转换逻辑复杂 | 高 | 高 | 先做简单场景,逐步完善 |
| 节点输出结构不确定 | 中 | 中 | 静态定义 + 运行时日志 |
7.2 技术验证(PoC)
Week 1: 核心技术验证
1. Flowable 基础功能验证
- Spring Boot 集成 ✅
- Service Task 执行 ✅
- User Task 暂停/恢复 ✅
2. 表达式引擎验证
- JUEL 性能测试 ✅
- 嵌套对象访问 ✅
- 数组索引访问 ✅
3. 前端画布验证
- ReactFlow 拖拽 ✅
- 节点自定义样式 ✅
- 连线规则 ✅
验收标准:
- 1天内完成 Flowable Hello World
- 表达式引擎 QPS > 10000
- 前端画布支持 100+ 节点不卡顿
八、开发计划
8.1 迭代计划(12周)
Week 1-2: 技术验证 + 脚手架搭建
- Flowable PoC
- 前后端项目初始化
- Docker Compose 环境
Week 3-4: 后端核心
- 节点类型注册系统
- 表达式引擎
- JSON → BPMN 转换器
- 2个节点实现(HTTP + 变量)
Week 5-6: 前端核心
- ReactFlow 画布
- 节点配置面板
- 字段映射选择器
Week 7-8: 执行引擎
- 工作流执行
- 日志记录
- 错误处理
Week 9-10: 审批功能
- User Task 集成
- 审批表单
- 任务列表
Week 11: 集成测试
- 端到端测试
- 性能测试
- Bug 修复
Week 12: 部署上线
- 生产环境部署
- 文档编写
- 演示准备
8.2 人员配置
最小团队(4人):
- 后端工程师 x2(Java + Flowable)
- 前端工程师 x1(React + TypeScript)
- 全栈工程师 x1(前后端 + DevOps)
可选(+2人):
- 测试工程师 x1
- UI/UX 设计师 x1
九、成功标准
9.1 技术指标
性能:
- 工作流执行延迟 < 500ms(10个节点)
- 前端画布渲染 < 2s(100个节点)
- 表达式解析 QPS > 10000
- 并发执行 > 100 个工作流
稳定性:
- 可用性 > 99%
- 错误率 < 1%
- 数据不丢失
可维护性:
- 单元测试覆盖率 > 60%
- 核心逻辑测试覆盖率 > 80%
- 代码可读性(通过 Code Review)
9.2 功能验收
场景1:创建简单工作流
1. 拖入 HTTP 节点,配置 URL: https://api.github.com/users/octocat
2. 拖入 邮件节点,配置收件人: ${nodes.http.output.body.email}
3. 连线:HTTP → 邮件
4. 保存并执行
5. 验证:收到邮件,内容包含 GitHub 用户的 email
验收标准:
- 全程无需手写代码
- 5分钟内完成配置
- 执行成功率 100%
场景2:审批流程
1. 创建工作流:HTTP请求 → 审批节点 → 邮件通知
2. 执行工作流
3. 验证:流程暂停在审批节点
4. 打开审批中心,看到待办任务
5. 批准
6. 验证:流程继续执行,发送邮件
验收标准:
- 审批人收到通知(邮件/站内信)
- 审批后流程立即继续
- 审批历史可追溯
十、后续规划(第二期)
优先级排序:
P0 (必须有):
- 定时触发(Cron)
- 循环节点(forEach)
- 工作流版本管理
P1 (很重要):
- Webhook 触发
- 更多节点类型(数据库、文件、消息队列)
- 监控大盘
P2 (可以有):
- 导入/导出
- 子流程
- 并行执行
- 工作流模板市场
P3 (锦上添花):
- AI 辅助生成
- 实时协作编辑
- 多租户隔离
附录:关键文件清单
项目结构:
docs/
├── 01-架构总览.md (本文档)
├── 02-后端技术设计.md (详细后端实现)
├── 03-前端技术设计.md (详细前端实现)
├── 04-数据模型设计.md (数据库表结构)
└── 05-开发规范.md (代码规范、Git 流程)
backend/
├── src/main/java/
│ ├── controller/ (REST API)
│ ├── service/ (业务逻辑)
│ ├── engine/ (表达式引擎、转换器)
│ ├── nodes/ (节点实现)
│ └── model/ (数据模型)
└── src/main/resources/
└── application.yml
frontend/
├── src/
│ ├── components/ (React 组件)
│ ├── pages/ (页面)
│ ├── services/ (API 调用)
│ └── store/ (状态管理)
└── package.json
docker-compose.yml
README.md
下一步:请查看详细的后端和前端技术设计文档。