@flowlab/all
Version:
A cool library focusing on handling various flows
212 lines (195 loc) • 9.49 kB
text/typescript
import { FlowLabEngine, BaseNode, INodeContext, StepType } from '../main/src'; // 假设库代码在此
// ---------- 定义各个操作对应的节点 ----------
// 节点 1: 获取用户信息 (继承 BaseNode 以便封装重试逻辑,虽然也可以在步骤配置里加)
class FetchUserNode extends BaseNode {
readonly metadata = { id: 'fetch-user', description: '获取用户信息' };
async execute(context: INodeContext): Promise<void> {
const userId = context.input.userId;
context.log(`获取用户 ${userId} ...`);
// 实际应调用 fetchUserService,这里直接模拟
await new Promise(res => setTimeout(res, 50));
if (Math.random() < 0.1 && context.retries === 0) { // 模拟第一次可能失败
throw new Error('用户服务暂时不可用 (模拟)');
}
context.output = { userId, email: `${userId}@test.com`, vipLevel: Math.random() > 0.5 ? 'VIP' : 'Normal' };
}
}
// 节点 2: 获取商品信息 (简单函数节点)
async function fetchProductNodeFunc(context: INodeContext) {
const productId = context.input.productId;
context.log(`获取商品 ${productId} ...`);
// 实际应调用 fetchProductService
await new Promise(res => setTimeout(res, 60));
if (Math.random() < 0.1 && context.retries === 0) { // 模拟第一次可能失败
throw new Error('商品服务暂时不可用 (模拟)');
}
const stock = Math.floor(Math.random() * 5);
context.output = { productId, price: 100, stock };
}
// 节点 3: 计算价格 (简单函数节点)
async function calculatePriceNodeFunc(context: INodeContext) {
const price = context.input.price;
const vipLevel = context.input.vipLevel;
let finalPrice = price;
if (vipLevel === 'VIP') {
finalPrice = price * 0.9;
context.log(`VIP 用户,应用折扣。最终价格: ${finalPrice}`);
} else {
context.log(`普通用户。最终价格: ${finalPrice}`);
}
context.output = { finalPrice }; // 设置输出
}
// 节点 4: 创建订单 (简单函数节点)
async function createOrderNodeFunc(context: INodeContext) {
const { userId, productId, price } = context.input;
context.log(`创建订单: 用户 ${userId}, 商品 ${productId}, 价格 ${price}`);
// 实际应调用 createOrderService
await new Promise(res => setTimeout(res, 70));
if (Math.random() < 0.05) {
throw new Error('订单服务创建失败 (模拟)');
}
context.output = { orderId: `order-${Date.now()}` };
}
// 节点 5: 发送成功通知 (简单函数节点)
async function sendSuccessEmailNodeFunc(context: INodeContext) {
const { email, orderId, productId, finalPrice } = context.input;
const subject = '订单创建成功';
const body = `您的订单 ${orderId} 已成功创建,商品 ${productId},价格 ${finalPrice}。`;
context.log(`发送成功邮件给 ${email}`);
// 实际应调用 sendEmailService
await new Promise(res => setTimeout(res, 40));
// 可以在这里设置 output 表示发送成功
}
// 节点 6: 发送失败通知 (简单函数节点)
async function sendFailureEmailNodeFunc(context: INodeContext) {
const { email, productId, reason } = context.input;
const subject = '订单创建失败';
const body = `抱歉,您的订单 (商品 ${productId}) 创建失败。原因: ${reason || '未知错误'}`;
context.log(`发送失败邮件给 ${email}`);
// 实际应调用 sendEmailService
await new Promise(res => setTimeout(res, 40));
}
// 节点 7: 记录库存不足 (简单函数节点)
async function logStockIssueNodeFunc(context: INodeContext) {
const productId = context.input.productId;
const stock = context.input.stock;
const message = `商品 ${productId} 库存不足 (${stock}),订单无法创建。`;
context.log(message);
// 可以将原因设置到变量,用于后续的失败通知
context.setVariable('failureReason', message);
// 这个节点本身是成功的,但它会导致流程走向失败路径
}
// ---------- 初始化 FlowLab 引擎并注册节点 ----------
const engine = new FlowLabEngine();
engine.registerNode(new FetchUserNode()); // 注册类节点
engine.registerNode('fetch-product', fetchProductNodeFunc, '获取商品信息'); // 注册函数节点
engine.registerNode('calculate-price', calculatePriceNodeFunc, '计算最终价格');
engine.registerNode('create-order', createOrderNodeFunc, '调用订单服务创建订单');
engine.registerNode('send-success-email', sendSuccessEmailNodeFunc, '发送成功通知邮件');
engine.registerNode('send-failure-email', sendFailureEmailNodeFunc, '发送失败通知邮件');
engine.registerNode('log-stock-issue', logStockIssueNodeFunc, '记录库存不足信息');
// ---------- 定义工作流 ----------
const orderWorkflow = engine.defineWorkflow('order-processing', '订单处理与通知流程')
.addParallel({ // 步骤 1 & 2: 并行获取用户和商品信息
id: 'fetch-data',
parallelSteps: [
{ // 并行分支 1: 获取用户
id: 'fetch-user-step', // 并行分支内的步骤也需要 ID (虽然通常不直接引用)
type: StepType.TASK, // 需要显式指定类型
nodeId: 'fetch-user',
inputMapping: { 'userId': 'input.userId' },
outputMapping: { 'variables.userInfo': 'output' }, // 将整个用户信息存入变量
retryOptions: { maxRetries: 2, delayMs: 100 } // 在步骤级别配置重试
},
{ // 并行分支 2: 获取商品
id: 'fetch-product-step',
type: StepType.TASK,
nodeId: 'fetch-product',
inputMapping: { 'productId': 'input.productId' },
outputMapping: { 'variables.productInfo': 'output' }, // 将整个商品信息存入变量
retryOptions: { maxRetries: 2, delayMs: 100 } // 配置重试
}
],
nextStepId: 'check-stock' // 所有并行步骤成功后,进入库存检查
})
.addCondition({ // 步骤 3: 检查库存
id: 'check-stock',
condition: (context) => (context.variables.productInfo?.stock ?? 0) > 0, // 检查变量中的库存
branches: {
'true': 'calculate-price', // 库存充足,去计算价格
'false': 'log-stock-issue' // 库存不足,记录问题
}
})
.addStep({ // 步骤 4 (库存不足路径): 记录库存问题
id: 'log-stock-issue',
nodeId: 'log-stock-issue',
inputMapping: { // 将商品信息传给日志节点
'productId': 'variables.productInfo.productId',
'stock': 'variables.productInfo.stock',
},
nextStepId: 'notify-failure' // 记录后直接去发送失败通知
})
.addStep({ // 步骤 5 (库存充足路径): 计算价格
id: 'calculate-price',
nodeId: 'calculate-price',
inputMapping: { // 从变量中获取价格和 VIP 等级
'price': 'variables.productInfo.price',
'vipLevel': 'variables.userInfo.vipLevel',
},
outputMapping: { // 将计算出的最终价格存入变量
'variables.finalPrice': 'output.finalPrice'
},
nextStepId: 'create-order' // 下一步创建订单
})
.addStep({ // 步骤 6: 创建订单
id: 'create-order',
nodeId: 'create-order',
inputMapping: { // 从变量和输入中获取所需信息
'userId': 'variables.userInfo.userId',
'productId': 'variables.productInfo.productId',
'price': 'variables.finalPrice'
},
outputMapping: { // 将订单 ID 存入变量
'variables.orderId': 'output.orderId'
},
retryOptions: { maxRetries: 1, delayMs: 200 }, // 配置重试
nextStepId: 'notify-success' // 成功则发送成功通知
// 注意:如果此步骤失败,默认会使整个工作流失败 (除非配置了错误处理分支或补偿)
})
.addStep({ // 步骤 7a (成功路径): 发送成功通知
id: 'notify-success',
nodeId: 'send-success-email',
inputMapping: { // 从变量中获取所需信息
'email': 'variables.userInfo.email',
'orderId': 'variables.orderId',
'productId': 'variables.productInfo.productId',
'finalPrice': 'variables.finalPrice'
}
// 流程结束
})
.addStep({ // 步骤 7b (失败路径, 从 log-stock-issue 或 create-order 失败后跳转过来 - 需要错误处理配置实现)
// 为了简化,我们假设从 log-stock-issue 直接跳过来
id: 'notify-failure',
nodeId: 'send-failure-email',
inputMapping: {
'email': 'variables.userInfo.email', // 假设 userInfo 总是能获取到
'productId': 'input.productId', // 从初始输入获取
'reason': 'variables.failureReason' // 从 log-stock-issue 设置的变量获取原因
}
// 流程结束
})
.setStartStep('fetch-data'); // 设置起始步骤
// (可选) 注册定义,以便按 ID 执行
engine.registerDefinition(orderWorkflow);
// ---------- 执行工作流 ----------
async function runFlowLabExample() {
const executor = engine.createExecutor();
const initialData = { userId: 'user1', productId: 'productA' };
console.log(`\n--- 使用 FlowLab 执行订单处理: 用户 ${initialData.userId}, 商品 ${initialData.productId} ---`);
const resultContext = await executor.run(orderWorkflow, initialData); // 直接用实例执行
console.log(`\n--- FlowLab 工作流执行完毕 ---`);
console.log(`最终状态: ${resultContext.status}`);
console.log(`最终变量:`, resultContext.variables);
// 可以查看 resultContext.history 获取详细步骤记录
}
runFlowLabExample();