1. 项目缘起:为什么我要在40天里“再造”一个Zapier?

如果你也像我一样,每天被各种SaaS工具之间的数据同步、任务流转搞得焦头烂额,那你一定能理解我的痛点。市场上有Zapier、Make(原Integromat)这样的自动化平台,它们确实强大,但问题也很明显:对于初创团队或个人开发者来说,订阅费用是一笔不小的开销;对于有定制化需求或数据安全顾虑的项目,闭源的SaaS服务又像是一个黑盒,你不知道你的数据流向了哪里,也无法在本地部署。更别提那些复杂的流程,一旦逻辑稍微绕一点,配置起来就让人头大。

于是,一个念头冒了出来:能不能自己做一个?一个开源的、能本地部署的、配置足够灵活的自动化工具。这个想法在脑子里盘旋了很久,直到我给自己设下了一个近乎疯狂的挑战: 用40天时间,从零开始,构建一个功能核心对标Zapier的开源自动化平台 。我给这个项目起名叫 HarshAI ,名字里的“Harsh”既代表了这个挑战的严苛,也暗示了我想让自动化流程的构建变得更直接、更“硬核”。

这40天,与其说是一次开发,不如说是一次极限的产品原型验证。我想证明,用现代的开发工具和架构思想,一个精简但五脏俱全的自动化核心是可以在短时间内被构建出来的。HarshAI的目标不是要完全复刻Zapier成千上万个集成,而是要提供一个坚实、可扩展的开源底座,让开发者可以基于它快速搭建属于自己的自动化工作流,或者将其作为中间件集成到自己的产品中。下面,我就把这40天里从架构设计、技术选型到具体实现踩过的坑、获得的经验,毫无保留地分享出来。

2. 核心架构设计:如何让“连接一切”变得可能?

构建一个自动化平台,最核心的抽象就是 “触发器(Trigger)- 动作(Action)” 模型。用户配置一个规则:“当A事件发生(触发器),则执行B操作(动作)”。但要让这个模型灵活运转,底层的架构设计至关重要。

2.1 事件驱动的中枢:工作流引擎

我放弃了从零编写一个复杂的状态机引擎,而是选择了 Node-RED 的核心思想进行精简和重构。Node-RED是一个基于流的编程工具,它用“节点(Node)”和“连线(Wire)”来可视化业务逻辑,这与自动化工作流的概念天然契合。

在HarshAI中,我设计了一个轻量级的 流执行引擎 。每个工作流(Workflow)都是一个有向无环图(DAG),图中的节点就是各种“应用集成节点”(比如“监听Github新Issue”、“发送Slack消息”、“查询Airtable记录”)。引擎的核心职责是:

  1. 解析与调度 :加载工作流JSON定义,根据节点间的依赖关系(连线)确定执行顺序。
  2. 上下文传递 :将一个节点的输出数据,作为下一个节点的输入上下文( msg.payload )。这里需要设计一个灵活的数据容器,能够承载各种结构的数据(字符串、对象、数组等)。
  3. 错误处理与重试 :当某个节点执行失败时,引擎需要捕获异常,并根据用户配置决定是终止流程、重试还是跳转到错误处理分支。

设计心得 :在早期版本,我试图让上下文对象包含所有历史节点的数据,这导致了内存膨胀和序列化困难。后来我优化为“仅传递上游直接依赖节点的必要输出”,并提供了一个全局的 flow 变量用于存储跨节点的共享数据,平衡了灵活性与性能。

2.2 连接器的抽象:统一千差万别的API

Zapier的强大在于其庞大的“应用库”。对于HarshAI,我需要设计一个可插拔的 连接器(Connector)框架 ,让开发者能方便地为任何具有API的服务编写集成。

我定义了一个基础的 BaseConnector 类,所有具体的连接器(如 GithubConnector , SlackConnector )都必须继承它。这个基类规定了几个关键方法:

  • authenticate(config) : 处理认证(API Key, OAuth等)。
  • getTriggers() : 返回该服务支持的所有触发器类型。
  • getActions() : 返回该服务支持的所有动作类型。
  • execute(nodeId, method, params) : 执行具体的触发器监听或动作调用。
// 一个简化版的连接器基类示例
class BaseConnector {
  constructor(credentials) {
    this.credentials = credentials;
    this.authenticated = false;
  }

  async authenticate() {
    throw new Error('Authentication method must be implemented');
  }

  async testConnection() {
    // 默认的连通性测试,可被重写
    try {
      await this.authenticate();
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  // 动态加载触发器/动作模块
  async loadModule(type, name) {
    const module = await import(`./${type}s/${name}.js`);
    return module;
  }
}

对于触发器,特别是需要轮询的(如“每隔5分钟检查新邮件”),我实现了一个 统一的调度器 。它管理着一个定时任务队列,当到达预定时间,就唤醒对应的工作流,并调用相应连接器的“抓取新数据”方法。对于支持Webhook的服务(如Github的Push事件),则提供了一个统一的Webhook端点,由路由层将收到的请求分发给对应的工作流。

2.3 数据流转与格式适配

不同API返回的数据格式天差地别。一个核心挑战是:如何让“监听Github Issue”节点输出的数据,能够被“创建Google Sheets行”的节点所理解?

我引入了 “数据映射(Data Mapping)” “模板渲染(Template Rendering)” 的概念。在每个动作节点配置中,用户可以使用类似 {{trigger.body.issue.title}} 的模板语法,来引用触发器节点的输出数据。引擎在执行动作前,会先用一个轻量级的模板引擎(如Handlebars)渲染最终的请求参数。

此外,我还设计了一个简单的 数据格式适配层 。对于一些常见的数据结构转换(比如将JSON对象数组转换为CSV字符串),提供内置的“函数节点”,用户可以在流程中插入这样的节点进行数据清洗和转换。

3. 关键技术栈选型与实战解析

40天的开发,技术选型必须追求“快、稳、准”。每一环的选择都直接影响了开发效率和最终系统的能力边界。

3.1 后端:Node.js + Fastify 的效能组合

选择 Node.js 是必然的,其非阻塞I/O模型非常适合处理自动化平台大量存在的网络I/O操作(调用各种API)。框架方面,我没有用最流行的Express,而是选择了 Fastify 。Fastify的性能开销更低,对JSON Schema的原生支持对于API请求验证来说简直是神器,能自动、高效地校验触发器/动作节点的输入配置,减少大量样板代码。

数据库方面,为了快速原型和部署简便,我选择了 SQLite 。它无需单独服务,一个文件搞定,对于早期版本和单体部署非常友好。我用 Prisma 作为ORM,它的类型安全性和直观的数据模型定义,让数据库操作既快又不容易出错。

// 使用Fastify和Prisma定义一个创建工作流的API端点
fastify.post('/workflows', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'definition'],
      properties: {
        name: { type: 'string' },
        definition: { type: 'object' } // 工作流JSON定义
      }
    }
  }
}, async (request, reply) => {
  const { name, definition } = request.body;
  const workflow = await prisma.workflow.create({
    data: {
      name,
      definition: JSON.stringify(definition),
      userId: request.user.id // 假设已有用户认证
    }
  });
  return { id: workflow.id, name: workflow.name };
});

3.2 前端:React + Zustand 的轻量级管理

前端需要一个能构建复杂交互界面(工作流编辑器)的框架, React 生态成熟,是稳妥的选择。状态管理上,我没有用Redux,而是选了 Zustand 。它的API极其简洁,概念少,对于管理工作流节点状态、画布视图状态这类中等复杂度的场景,Zustand写起来更痛快,几乎没有心智负担。

工作流编辑器是UI的核心,我评估了React Flow和X6,最终选择了 React Flow 。它开箱即用的节点拖拽、连线、缩放手势支持,以及活跃的社区,让我能专注于业务逻辑(如节点配置表单、数据预览)而非图形交互底层。

3.3 部署与运行时:Docker化与进程隔离

为了让HarshAI能够一键部署, Docker 是标配。我编写了多阶段的Dockerfile,优化了生产环境镜像的体积。

一个关键的安全与稳定性考量是 进程隔离 。最初,所有工作流都在主Node.js进程中运行,一个工作流中的死循环或内存泄漏会导致整个服务崩溃。为了解决这个问题,我引入了 Worker Threads (工作线程)机制。每个工作流实例都在一个独立的工作线程中执行,与主进程隔离。这样,单个工作流的崩溃不会影响平台和其他工作流,主进程只需要监听和重启异常的工作线程即可。

踩坑实录 :在实现工作线程通信时,最初我尝试传递复杂的函数和闭包,这导致了序列化错误。后来严格规定主线程与工作线程之间只通过可序列化的JSON对象传递消息,所有业务逻辑都封装在工作线程侧加载的模块里,问题才得以解决。

4. 核心功能实现深度拆解

4.1 可视化工作流编辑器的实现

编辑器不仅要能画图,更要能精准地配置每一个节点。我将其拆解为三个核心部分:

  1. 画布渲染与交互层(React Flow) :负责节点的拖拽、布局、连线的绘制与交互。这里的关键是自定义节点类型,除了基础的输入/输出节点,每个集成都对应一个独特的节点组件,显示其图标和名称。
  2. 节点配置管理器 :这是业务逻辑的核心。当用户点击画布上的一个“Github触发器”节点时,右侧面板需要动态加载该节点的配置表单。我实现了一个基于JSON Schema的动态表单生成器。每个连接器在定义其触发器/动作时,都需要附带一个配置的JSON Schema(描述需要哪些字段,字段是什么类型,是否必填等)。前端根据这个Schema实时渲染出表单,并利用Ajv库进行前端验证。
  3. 工作流定义持久化 :画布上的所有节点和连线状态,最终需要被序列化为一个HarshAI引擎能理解的JSON定义。这个定义包括了节点列表、节点间的连接关系、每个节点的配置参数。当用户点击保存时,前端状态被序列化,通过API保存至数据库。

4.2 多类型触发器的调度策略

触发器是工作流的起点,我实现了三种主要类型:

  • 轮询触发器(Polling Trigger) :最通用,也最消耗资源。我实现了一个基于 node-cron 的分布式友好型调度器。它在数据库里维护一个“触发器任务表”,记录下次执行时间。一个后台守护进程定期扫描这张表,将到期的任务放入执行队列。难点在于避免重复执行和错过执行,我采用了“乐观锁”机制:任务执行前,先原子性地更新其“下次执行时间”,更新成功者获得执行权。
  • Webhook触发器 :效率最高。我为平台分配了一个统一的Webhook接收端点(如 https://your-harshai.com/webhook/:workflow_id/:trigger_id )。用户需要在第三方服务(如Github)中将此URL配置为Webhook。当事件发生时,第三方服务会主动推送数据到这个端点,平台再将其转发给对应的工作流。这里的安全关键是验证签名,例如Github的X-Hub-Signature,防止伪造请求。
  • 即时触发器(Instant Trigger) :这通常由平台内部的另一个工作流或API调用触发,用于构建工作流链。我暴露了一个REST API,允许通过调用 POST /trigger/:trigger_id 并携带数据来手动触发一个工作流。

4.3 认证系统的安全设计

集成第三方服务,认证是头等大事。HarshAI需要安全地存储用户的API密钥、OAuth Token等敏感信息。

  • 凭证存储 :所有凭证在存入数据库前,都必须经过加密。我使用Node.js的 crypto 模块,结合一个从环境变量读取的、独立于数据库的密钥进行AES-256-GCM加密。这样即使数据库泄露,攻击者也无法直接拿到明文凭证。
  • OAuth流程 :对于支持OAuth 2.0的服务(如Google, Slack),我实现了一个标准的授权码(Authorization Code)流程。平台作为OAuth客户端,引导用户到服务商页面授权,获取授权码后,在后端用授权码+客户端密钥交换访问令牌。 绝对禁止 在前端进行此操作,以防客户端密钥泄露。
  • 凭证刷新 :许多OAuth Token有过期时间。我设计了一个后台任务,定期检查即将过期的令牌,并使用刷新令牌(如果提供)自动获取新令牌,确保长周期工作流不会因认证失败而中断。

5. 开发历程:40天极限挑战的节奏与里程碑

这40天被划分为几个明确的冲刺阶段,每个阶段都有明确的可交付成果。

第一周(Day 1-7):核心引擎与最小闭环 目标:实现一个能跑通“定时触发器 -> 执行一个简单HTTP请求”的引擎。

  • 完成项目基础框架搭建(前后端、数据库)。
  • 实现最基础的工作流DAG解析与线性执行引擎。
  • 实现一个“HTTP请求”动作连接器和一个“定时”触发器。
  • 成果:在控制台成功配置一个每10分钟访问一次某API的工作流。

第二至三周(Day 8-21):连接器框架与关键集成 目标:让平台真正“有用”,支持开发者扩展。

  • 设计并完成可插拔的连接器框架。
  • 开发5-7个最常用的连接器(如Github, Slack, Airtable, Discord, 发送邮件)。
  • 实现Webhook触发器和对应的连接器支持。
  • 实现前端动态表单渲染。
  • 成果:可以配置“Github有新Issue时,发送通知到Slack频道”这样的实用工作流。

第四周(Day 22-28):用户体验与稳定性 目标:让产品可用、易用、稳定。

  • 完善可视化编辑器,优化拖拽、连线体验。
  • 实现工作流的导入/导出、复制功能。
  • 引入工作线程进行进程隔离。
  • 添加全面的错误处理、执行日志和简单监控。
  • 成果:拥有一个功能完整、界面友好、运行稳定的Web应用。

第五周(Day 29-35):部署与文档 目标:让任何人能轻松尝试。

  • 编写完整的Dockerfile和docker-compose.yml。
  • 编写部署文档(包括环境变量配置、反向代理设置)。
  • 编写用户使用指南和开发者编写新连接器的教程。
  • 搭建基础的项目文档网站。

最后五天(Day 36-40):收尾、测试与发布 目标:发布一个可用的初始版本。

  • 进行集成测试和压力测试(模拟大量并发工作流)。
  • 修复最后一刻发现的Bug。
  • 准备GitHub仓库的README、LICENSE。
  • 正式在GitHub上开源发布v0.1.0版本。

6. 遇到的典型问题与实战解决方案

在开发过程中,一些反复出现的问题构成了主要的挑战。

6.1 异步流程控制与错误处理

自动化工作流本质是异步操作的串联。一个常见的陷阱是:动作B依赖于动作A的结果,但A是一个异步HTTP请求。我最初用简单的Promise链,但当流程复杂、需要条件分支时,代码迅速变得难以维护。

解决方案 :我引入了一个基于 异步队列 的执行器。引擎将工作流解析为一个任务队列,每个任务(节点)都是一个返回Promise的函数。执行器按顺序(或并行,如果节点间无依赖)消费队列,并将上一个任务的结果注入下一个任务的上下文。错误处理被统一包装:每个任务都被 try...catch 包裹,错误会携带节点ID和错误信息被抛给上层,由工作流级别的错误处理策略(如“失败后重试3次”)决定下一步行动。

6.2 第三方API的速率限制与容错

免费或低阶的API通常有严格的速率限制(如Github API每小时60次)。一个配置不当的轮询触发器可能在几分钟内就把配额用光。

解决方案 :我在连接器框架层面增加了 请求队列与限流器 。对于每个服务(以API Key或Token区分),维护一个令牌桶(Token Bucket)或滑动窗口计数器。所有通过该连接器发起的请求都必须先通过限流器。如果触达限制,请求会被延迟或放入队列等待。同时,对所有HTTP请求实现指数退避重试机制,特别是对于5xx服务器错误。

6.3 工作流版本管理与回滚

用户修改了一个正在运行的工作流,可能会引入错误。如何保证平滑更新和快速回滚?

解决方案 :我为每个工作流引入了 版本概念 。每次保存都创建一个新版本,但只有一个是“活跃版本”。工作流引擎只执行活跃版本。当用户保存新配置时,系统先验证新版本的定义是否合法(如无循环依赖),然后将其创建为新的“草稿版本”。用户手动“发布”后,该版本成为新的活跃版本,旧版本被存档。如果新版本运行出现问题,用户可以一键将活跃版本切换回之前的稳定版本。

6.4 常见问题速查表

问题现象 可能原因 排查步骤与解决方案
工作流配置后不触发 1. 触发器未激活
2. 认证失败
3. 调度器服务未运行
1. 检查工作流“启用”开关是否打开。
2. 在连接器配置页面测试认证是否成功。
3. 查看后台服务日志,确认调度器进程是否正常启动。
动作节点执行失败,报“网络错误” 1. 目标服务不可用
2. 请求参数格式错误
3. 触发速率限制
1. 检查目标服务状态(如访问其官网)。
2. 查看该节点的输入数据预览,确认模板渲染后的参数符合API文档要求。
3. 查看连接器的执行日志,确认是否收到“429 Too Many Requests”响应,若是则调整触发频率。
Webhook触发器收不到数据 1. Webhook URL配置错误
2. 网络防火墙/安全组拦截
3. 第三方服务未发送事件
1. 核对HarshAI生成的Webhook URL是否完整无误地填入第三方服务。
2. 确保HarshAI服务器所在环境的80/443端口可公开访问。
3. 在第三方服务界面手动触发一个测试事件,并查看其Webhook发送日志。
工作流执行日志丢失 1. 日志级别设置过高
2. 数据库连接问题
3. 日志清理任务误删
1. 检查环境变量 LOG_LEVEL ,设置为 debug info
2. 检查数据库服务是否正常,表结构是否完整。
3. 确认自动清理日志的定时任务配置是否过于激进。

7. 开源后的思考与项目展望

40天的极限挑战结束了,HarshAI v0.1.0也如期在GitHub上开源。它具备了自动化平台最核心的骨架:可视化编辑、可扩展的连接器框架、多种触发器、以及稳定的执行引擎。但我知道,这仅仅是一个开始。

开源后,我收到了不少反馈。有的开发者希望集成国内常用的服务,如钉钉、飞书、企业微信;有的用户希望有更复杂的分支逻辑(if/else,循环);还有的团队询问高可用和水平扩展的方案。这些正是HarshAI未来迭代的方向。

我个人最大的体会是: 构建一个“可用”的原型比想象中快,但打造一个“好用且可靠”的产品需要持续的打磨 。这40天里,大约70%的时间花在了核心架构和基础功能上,而剩下30%的时间,几乎全部投入在了错误处理、边缘情况、用户体验和部署优化这些“非功能性需求”上。这些才是决定一个开源项目能否真正被他人采纳使用的关键。

对于想借鉴或参与这个项目的朋友,我的建议是:先从编写一个自己的连接器开始。连接器框架的设计目标是降低扩展门槛。你可以挑一个你熟悉且常用的API服务,参照现有的示例,实现三部分:认证、一个触发器、一个动作。这个过程能让你最快地理解HarshAI的内部数据流和设计哲学。

HarshAI不会,也不可能在功能数量上短期内超越成熟的商业产品。它的价值在于提供了一个 自主、可控、可深度定制的自动化基座 。你可以把它部署在内网,连接内部系统;可以修改它的代码,适配特殊的业务逻辑;可以以它为蓝本,开发出更适合某一垂直领域的自动化工具。这,就是开源的力量,也是我发起这个挑战的初衷。

更多推荐