1. 项目概述:一个开源自动化工具的诞生

“用40天时间,造一个Zapier的替代品,并且开源。” 这听起来像是一个技术狂人的豪言壮语,但HarshAI这个项目确实做到了。作为一名长期在自动化集成领域摸爬滚打的开发者,我深知像Zapier、Make这类无代码自动化平台的价值,它们极大地降低了连接不同应用的门槛。然而,它们的封闭性、高昂的按触发次数收费模式,以及对复杂逻辑处理能力的限制,始终是悬在企业和开发者头上的达摩克利斯之剑。于是,一个想法诞生了:能否构建一个功能强大、完全开源、可自托管、且具备高度可扩展性的自动化平台?这就是HarshAI的起点。

HarshAI的核心定位是一个开源的自动化工作流引擎,它允许用户通过可视化的方式,或者直接通过代码,将不同的网络服务、API、数据库乃至本地应用连接起来,形成自动化的“如果…那么…”逻辑链。与商业平台最大的不同在于,它将控制权完全交还给用户。你可以在自己的服务器上部署它,所有数据都在你的掌控之中,无需担心隐私泄露或供应商锁定。更重要的是,开源意味着你可以无限定制它,为它添加任何你需要的“连接器”,或者修改其核心逻辑以适应极端特殊的业务场景。在短短40天内从零到一实现这样一个系统,不仅是对技术选型和架构设计的考验,更是一次对现代开发流程极限的挑战。

2. 核心架构设计与技术选型

2.1 为什么选择“微服务+事件驱动”架构?

面对一个需要连接无数异构系统、处理高并发工作流执行请求的平台,单体架构是首先被排除的选项。我选择了“微服务+事件驱动”作为HarshAI的基石。微服务架构将系统拆分为多个松耦合、独立部署的服务,例如:工作流定义服务、触发器监听服务、动作执行服务、连接器管理服务、用户认证服务等。这样做的好处显而易见:每个服务可以独立开发、部署和扩展。当工作流执行压力大时,可以单独扩容“动作执行服务”的实例,而不会影响用户管理功能。

事件驱动则是连接这些微服务的“神经系统”。所有内部通信,如“一个触发器被激活”、“一个动作执行完成”、“需要记录日志”,都通过一个中央消息队列(如RabbitMQ或Apache Kafka)以事件的形式发布和订阅。这种异步通信方式解耦了服务间的直接依赖,提高了系统的整体弹性和响应能力。例如,当某个API调用特别慢时,它不会阻塞整个工作流引擎,事件会被持久化在队列中,等待后续处理。

注意 :事件驱动架构虽然强大,但也引入了复杂性,如事件顺序性、幂等性处理和死信队列的管理。在HarshAI的初期,我选择了相对简单的AMQP协议实现,并确保所有关键事件都具备唯一ID和重试机制,这是保证系统可靠性的关键。

2.2 技术栈的深度考量:速度与可控性的平衡

40天的极限开发周期,意味着必须在“追求最新技术”和“稳定可控”之间做出精准权衡。我的选型原则是:核心框架成熟稳定,开发工具链高效现代。

  • 后端核心(Node.js + TypeScript) :Node.js的非阻塞I/O模型非常适合I/O密集型的自动化任务,比如频繁的HTTP API调用、数据库查询。TypeScript的强类型系统在项目复杂度快速上升时,提供了无可替代的代码智能提示和重构安全性,极大减少了动态语言在后期可能出现的隐蔽错误。
  • 工作流引擎(自研DSL + 可视化编辑器) :我没有直接采用像Camunda这样重量级的工作流引擎,因为它们过于庞大且定制困难。相反,我定义了一套简洁的JSON DSL来描述工作流。一个工作流本质上就是一个由“节点”和“边”组成的有向无环图。每个节点代表一个步骤(触发器或动作),边代表执行路径和条件逻辑。前端基于React Flow库实现了可视化编辑器,将JSON DSL图形化,用户拖拽即可设计流程。
  • 连接器生态(动态加载与模板) :连接器是平台的灵魂。我设计了一个通用的“连接器基类”,任何新的服务集成(如Slack、Google Sheets、GitHub)只需要继承这个基类,实现 auth trigger action 等几个标准方法,并提供一个定义输入输出参数的Schema即可。这些连接器以NPM包或独立模块的形式存在,支持动态热加载,无需重启主服务就能扩展平台能力。
  • 数据存储(PostgreSQL + Redis) :PostgreSQL用于存储所有核心关系数据,如用户信息、工作流定义、执行历史记录。其强大的JSONB字段类型非常适合存储工作流节点中动态的配置数据。Redis则作为缓存和消息队列的支撑,存储用户会话、临时执行状态以及作为Celery类任务队列的后端,加速高频数据的读写。
  • 部署与运维(Docker + Docker Compose) :一键部署是开源项目吸引用户的重要一环。我使用Docker将每个微服务容器化,并通过Docker Compose编排文件定义它们之间的依赖和网络关系。用户只需 docker-compose up -d 就能在本地或服务器上启动完整的HarshAI环境,极大降低了尝鲜和使用的门槛。

3. 核心功能模块拆解与实现

3.1 工作流设计器:从可视化到可执行代码

工作流设计器的目标是让非开发者也能轻松构建自动化流程。其核心是将用户在前端的图形化操作,实时转化为后端可执行的JSON DSL。

前端实现 :基于React Flow,我定义了两种节点类型: TriggerNode ActionNode 。每个节点组件上都有一个配置面板,用户点击节点后,右侧会动态加载该连接器对应的配置表单(基于JSON Schema生成)。当用户从节点的输出锚点拖出一条连接线到另一个节点的输入锚点时,前端会在内部数据结构中建立一条“边”,并允许用户为这条边设置条件(例如:“仅当上一个步骤的输出中包含‘error’关键词时才执行此分支”)。

DSL转换 :当用户点击保存时,前端会将当前的图数据(节点和边)序列化为一个特定的JSON结构。这个结构包含了工作流的元数据、一个按执行顺序或依赖关系排列的节点列表,以及节点间的跳转逻辑。例如:

{
  "version": "1.0",
  "name": "新用户欢迎流程",
  "trigger": {
    "id": "trigger_1",
    "type": "webhook",
    "config": { "path": "/webhook/new-user" }
  },
  "actions": [
    {
      "id": "action_1",
      "type": "sendgrid.send_email",
      "config": { "to": "{{trigger.body.email}}", "template": "welcome" },
      "dependsOn": ["trigger_1"]
    },
    {
      "id": "action_2",
      "type": "slack.send_message",
      "config": { "channel": "#general", "text": "新用户 {{trigger.body.name}} 加入了!" },
      "dependsOn": ["trigger_1"]
    }
  ]
}

这个JSON就是工作流的“源代码”。后端的工作流引擎会解析这个DSL,将其编译成可执行的任务链。

3.2 触发器与动作执行引擎:可靠性的核心

这是整个系统最核心、最复杂的部分。它的职责是:持续监听各种触发事件,一旦事件发生,就找到对应的工作流实例,并严格按照DSL的定义,可靠地、按顺序地执行每一个动作。

触发器监听服务 :这是一个常驻的微服务。它内部维护了一个注册表,记录了所有已启用工作流的触发器信息。对于“Webhook”类触发器,它会动态注册路由;对于“轮询”类触发器(如每5分钟检查一次Gmail新邮件),它会启动定时任务。当事件到达时,服务会创建一个唯一的“工作流执行实例”,将触发器的输出数据作为初始上下文,并将其发布到“工作流执行队列”。

动作执行服务 :这是多个无状态的工作进程,从“工作流执行队列”中消费任务。每个任务包含工作流实例ID和当前要执行的节点ID。执行服务的工作流程如下:

  1. 加载上下文 :从共享存储(Redis)中获取该工作流实例的当前执行上下文(一个包含了之前所有步骤输出数据的对象)。
  2. 解析节点 :根据节点ID从DSL中找到对应的动作定义,包括其类型(如 http.request )和配置。
  3. 渲染配置 :动作的配置中可能包含模板变量,如 {{trigger.body.email}} {{steps.action_1.output.statusCode}} 。执行引擎需要从当前上下文中解析出这些变量的真实值。
  4. 动态加载与执行连接器 :根据动作类型,动态加载对应的连接器模块,调用其 run 方法,并传入渲染后的配置和当前上下文。
  5. 处理结果与错误 :将连接器执行的结果(成功输出或错误信息)保存到上下文中。如果成功,则根据DSL中的边条件决定下一个要执行的节点,并生成新的任务放入队列。如果失败,则根据工作流设置的“重试策略”(如“最多重试3次,间隔10秒”)进行处理,或标记该实例为失败。
  6. 持久化与日志 :每一步的执行结果、耗时和日志都会写回数据库,供用户查看详细的执行历史。

实操心得 :在实现执行引擎时,最大的挑战是保证“幂等性”和“错误恢复”。我采用了“至少一次”的投递语义,这意味着同一个任务可能被多次消费。因此,在每个动作执行前,我都会检查Redis中该动作在本实例中是否已执行成功(通过一个唯一的键),如果已成功,则直接跳过并复用之前的结果,避免重复发送邮件或创建重复数据。这对于网络不稳定或服务重启的情况至关重要。

3.3 连接器开发框架:生态扩展的基石

一个自动化平台的潜力取决于其能连接多少服务。为了让社区能够轻松贡献连接器,我设计了一个极简但功能完整的连接器开发框架。

核心接口 :每个连接器都是一个独立的类,必须实现以下接口:

  • constructor(config) : 接收用户配置(如API密钥、账号信息)。
  • async test() : 用于测试连接是否有效。
  • async run(operation, input, context) : 执行具体的操作(如 send_message ), input 是动作配置渲染后的值, context 是整个工作流的上下文。

元数据定义 :每个连接器还需要导出一个 manifest 对象,用JSON Schema描述它支持的触发器、动作,以及每个操作的输入参数格式。这个 manifest 会被设计器前端用来生成配置表单。

示例:一个简单的HTTP请求连接器

// connectors/http-request/manifest.json
{
  "name": "HTTP Request",
  "version": "1.0.0",
  "actions": {
    "make_request": {
      "title": "发起HTTP请求",
      "description": "向指定的URL发送HTTP请求",
      "inputSchema": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "method": { "type": "string", "enum": ["GET", "POST", "PUT", "DELETE"] },
          "headers": { "type": "object" },
          "body": { "type": "string" }
        },
        "required": ["url", "method"]
      }
    }
  }
}

// connectors/http-request/index.js
class HttpRequestConnector {
  async run(operation, input) {
    const { url, method, headers, body } = input;
    const response = await fetch(url, { method, headers, body });
    const responseData = await response.text();
    return {
      success: response.ok,
      statusCode: response.status,
      headers: Object.fromEntries(response.headers),
      body: responseData
    };
  }
}
module.exports = HttpRequestConnector;

通过这种方式,开发者无需了解HarshAI的核心代码,只需遵循这个简单的模式,就能为平台添加一个新的服务集成。项目初期我内置了十多个常用连接器(如HTTP、Email、Slack、GitHub),为社区贡献打下了基础。

4. 开发历程:40天极限挑战的实战记录

4.1 第一阶段:核心引擎与最小可行产品(第1-15天)

前两周的目标是跑通一个最核心的闭环:用户能创建一个由Webhook触发、经过一个HTTP动作、最终将结果记录到日志的工作流。

  • 第1-3天:项目奠基 。初始化Monorepo代码结构,搭建基于Docker的开发环境,确定技术栈,完成用户认证和项目管理的基础API。
  • 第4-7天:工作流DSL与设计器原型 。定义出第一版JSON DSL格式,并实现一个极其简陋的前端设计器(只能添加节点和连线)。同时,后端开始构建工作流模型的CRUD API。
  • 第8-12天:执行引擎雏形 。实现最关键的“触发器监听-队列-动作执行”循环。此时队列用的是内存队列,连接器只有内置的 webhook http.request 。但已经可以完成“接收一个POST请求 -> 转发到另一个URL -> 打印结果”这个完整流程。这是第一个激动人心的时刻。
  • 第13-15天:完善与测试 。为执行引擎添加基本的错误处理和日志,编写端到端测试,确保核心流程稳固。发布第一个可运行的Docker镜像到Docker Hub。

这个阶段遵循了严格的“深度优先”开发策略,即不顾界面美观和功能丰富,全力保障核心链路畅通。这避免了在枝节问题上过度消耗时间。

4.2 第二阶段:功能强化与稳定性建设(第16-30天)

核心跑通后,接下来是填充血肉,让产品真正可用。

  • 第16-20天:丰富连接器与变量系统 。实现了 email database (基础查询)、 delay (延迟等待)等关键内置连接器。设计并实现了强大的模板变量系统,支持从触发器、历史步骤、甚至全局变量中取值。
  • 第21-25天:条件分支与循环逻辑 。扩展DSL和设计器,支持基于变量值的条件分支(if/else)和简单的循环(for each)节点。这使工作流从线性管道变成了真正的逻辑流程图,能力大幅提升。
  • 第26-28天:用户界面优化与体验提升 。重构前端设计器,使其交互更流畅,增加节点库面板、实时保存、导入导出等功能。完善工作流执行历史查看页面,可以清晰看到每一步的输入输出和状态。
  • 第29-30天:性能优化与监控 。引入Redis缓存频繁访问的数据(如工作流定义)。为执行引擎添加简单的指标收集(如每秒执行任务数、平均耗时),并通过一个管理面板展示。开始考虑将内存队列替换为更可靠的RabbitMQ。

4.3 第三阶段:打磨、文档与开源发布(第31-40天)

最后十天是冲刺,目标是让项目达到可被他人理解和使用的状态。

  • 第31-35天:连接器开发框架与文档 。将连接器系统抽象成清晰的框架,并编写详细的《连接器开发指南》。同时,撰写完整的项目README,包括特性介绍、快速开始、架构说明和API文档。
  • 第36-38天:测试与部署优化 。编写覆盖核心模块的单元测试和集成测试。优化Docker Compose配置,确保生产环境部署更简单(配置环境变量、数据持久化卷等)。创建了一个一键部署脚本。
  • 第39-40天:最终整理与发布 。进行全面的代码审查和清理。在GitHub上创建仓库,精心编写项目介绍,选择合适的开源协议(MIT),然后将代码推送到公开仓库。撰写第一篇介绍博客,在相关的技术社区(如Hacker News, Reddit的r/selfhosted)发布。

这40天是高度密集和专注的。关键在于严格的优先级排序、对“完美主义”的克制(优先实现可用,再考虑优化),以及充分利用了现代开发工具链(如热重载、容器化)带来的效率提升。

5. 部署、运维与性能调优指南

5.1 从开发到生产:部署架构详解

对于个人或小团队,使用提供的 docker-compose.yml 单机部署是最快的方式。但对于需要高可用的生产环境,建议采用以下分布式部署架构:

[负载均衡器 (Nginx/Traefik)]
        |
        v
[Web前端服务] (可多实例)
        |
        v
[API网关服务] (可多实例) ---> [认证服务]
        |                           |
        v                           v
[消息队列 (RabbitMQ)]        [数据库 (PostgreSQL)]
        |                           |
        v                           v
[触发器监听服务] (可多实例)   [缓存 (Redis)]
        |                           |
        v                           v
[动作执行服务] (可多实例) <--- [共享存储 (用于上下文)]

关键配置说明

  • 环境变量 :所有服务的配置(数据库连接串、Redis地址、JWT密钥、外部API密钥等)都应通过环境变量注入,确保配置与代码分离。
  • 数据持久化 :PostgreSQL、Redis和RabbitMQ的数据目录必须挂载到宿主机的持久化卷上,防止容器重启数据丢失。
  • 日志收集 :将所有容器的日志输出到标准输出,然后使用Docker的日志驱动或单独的日志收集器(如Fluentd)统一收集到ELK或Loki等系统中,便于排查问题。
  • 健康检查 :在Docker Compose或Kubernetes配置中为每个服务设置 healthcheck ,确保编排工具能自动重启不健康的实例。

5.2 监控、告警与日常运维

开源版本自带了一个简单的管理面板,显示系统状态和基本指标。对于生产环境,需要集成更专业的监控栈。

  • 应用性能监控 :在Node.js服务中集成像 OpenTelemetry 这样的工具,自动追踪工作流执行的链路,将数据发送到Jaeger或Zipkin,可以清晰看到一个慢请求到底卡在哪个连接器或哪一步。
  • 系统指标监控 :使用Prometheus收集服务器和容器的CPU、内存、磁盘、网络指标,以及RabbitMQ队列长度、PostgreSQL连接数等。通过Grafana制作仪表盘。
  • 业务指标监控 :在代码关键位置埋点,记录如“每日工作流执行总数”、“各连接器调用成功率”、“平均执行耗时”等业务指标,这对于了解平台使用情况和发现潜在问题至关重要。
  • 告警设置 :在Prometheus Alertmanager或Grafana中设置告警规则,例如:“RabbitMQ队列积压超过1000”、“API网关5xx错误率超过1%”、“某个关键工作流连续失败3次”,确保问题能及时被发现。

日常运维清单

  1. 定期备份 :定时备份PostgreSQL数据库和重要的配置文件。
  2. 日志巡检 :每天查看错误日志,关注是否有异常增长的模式。
  3. 资源检查 :监控服务器磁盘空间、内存使用情况,提前规划扩容。
  4. 依赖更新 :定期更新Docker基础镜像、Node.js依赖包,修复安全漏洞。
  5. 连接器维护 :关注第三方API的变更通知,及时更新对应的连接器。

5.3 性能瓶颈分析与调优实战

随着工作流数量和复杂度增加,性能问题会逐渐暴露。以下是我在压力测试和实际使用中遇到的典型瓶颈及解决方案:

瓶颈一:数据库连接数耗尽

  • 现象 :动作执行服务实例增多后,出现“Sorry, too many clients already”数据库错误。
  • 根因 :每个服务实例都维护着自己的数据库连接池,实例数*池大小可能超过PostgreSQL的 max_connections 限制。
  • 解决方案
    1. 优化每个服务的连接池配置,根据实际负载减小 max 值。
    2. 在PostgreSQL前配置连接池中间件,如PgBouncer,它可以在应用和数据库之间管理复用连接,将物理连接数减少一个数量级。
    3. 适当调高PostgreSQL的 max_connections 参数(需考虑服务器内存)。

瓶颈二:Redis成为单点热点

  • 现象 :工作流执行上下文频繁读写Redis,在高并发下Redis CPU使用率飙升,延迟增加。
  • 根因 :所有工作流实例的中间状态都集中在单个Redis实例中。
  • 解决方案
    1. 分片 :根据工作流ID或用户ID对Key进行分片,将数据分布到多个Redis实例上。
    2. 本地缓存 :对于执行生命周期内频繁读取的上下文数据,在执行服务的内存中进行本地缓存,减少对Redis的访问。
    3. 优化数据结构 :评估是否所有数据都需要存Redis。对于大的、不常变的附件或结果,可以存对象存储(如MinIO),Redis只存引用。

瓶颈三:同步HTTP调用阻塞

  • 现象 :工作流中某个调用外部慢API的动作会阻塞整个执行线程,影响其他工作流的吞吐量。
  • 根因 :Node.js虽然是异步的,但一个工作流中的动作默认是顺序执行的,一个慢动作会拖慢整个流程。
  • 解决方案
    1. 异步动作 :对于不关心即时结果的后续动作,可以将其标记为“异步”。引擎会将其放入队列后立即返回,不等待其完成,从而实现“准并行”执行。
    2. 增加执行器实例 :水平扩展动作执行服务的实例数量,这是应对I/O密集型任务最直接有效的方法。
    3. 连接器优化 :在连接器内部为耗时操作设置合理的超时时间,并实现重试和熔断机制,避免一个外部服务的故障拖垮整个平台。

6. 常见问题排查与社区生态建设

6.1 故障排查手册:从现象到根因

在实际运行中,你会遇到各种各样的问题。下面是一个快速排查指南:

现象 可能原因 排查步骤
工作流未被触发 1. 触发器未激活
2. 监听服务宕机
3. 网络/防火墙问题
1. 检查工作流是否处于“启用”状态。
2. 查看触发器监听服务的日志,确认其是否正常启动并注册了监听器。
3. 对于Webhook,使用 curl 或Postman手动发送测试请求,查看API网关日志是否收到。
工作流执行失败 1. 动作配置错误
2. 连接器认证失败
3. 外部API不可用或限流
4. 模板变量渲染错误
1. 在“执行历史”中查看失败步骤的详细日志和错误信息。
2. 检查该动作的输入配置,特别是API密钥、URL等。
3. 手动测试连接器的 test 方法,验证认证是否有效。
4. 检查模板变量语法和引用的上下文路径是否正确。
工作流执行超时 1. 单个动作耗时过长
2. 队列积压,任务等待时间过长
3. 执行器资源不足
1. 查看该动作的日志,确定其执行了多久,是否在等待外部响应。
2. 检查RabbitMQ管理界面,查看队列深度和消费者数量。
3. 监控服务器CPU/内存使用率,考虑增加执行器实例。
设计器保存失败 1. 前端DSL生成逻辑错误
2. 后端API验证不通过
3. 网络中断
1. 打开浏览器开发者工具,查看网络请求的响应信息。
2. 检查后端API日志,看是否有数据验证错误(如JSON Schema不匹配)。
3. 确保工作流中没有循环依赖或无效的节点连接。

一个真实案例 :用户报告一个发送邮件的动作间歇性失败。日志显示“SMTP Connection Timeout”。排查发现,用户使用的公共SMTP服务器有频率限制。解决方案不是修改平台代码,而是在该连接器的 run 方法中增加了指数退避重试机制,并将SMTP连接池化复用,问题得以解决。这提示我们,连接器的健壮性需要针对具体的第三方服务特性进行增强。

6.2 参与贡献与生态扩展

HarshAI作为一个开源项目,其长期生命力依赖于社区。吸引和帮助贡献者是关键。

如何贡献一个连接器? 这是最常见的贡献方式。步骤非常清晰:

  1. Fork仓库 :在GitHub上Fork主项目。
  2. 创建分支 :基于 develop 分支创建一个新分支,如 feat/connector-stripe
  3. 开发连接器 :在 connectors/ 目录下创建一个新文件夹,按照框架要求实现连接器类和 manifest.json 。务必编写清晰的JSDoc注释。
  4. 编写测试 :为你的连接器编写单元测试,确保核心功能正常。
  5. 更新文档 :在 docs/connectors/ 目录下创建一个Markdown文件,介绍该连接器的安装、配置和使用方法,并提供一个简单的工作流示例。
  6. 提交PR :将更改推送到你的Fork,并向主项目的 develop 分支发起Pull Request。在PR描述中详细说明你的连接器功能和测试情况。

项目维护者的工作

  • 代码审查 :仔细审查每个PR,确保代码质量、符合项目规范且没有安全漏洞。
  • CI/CD :维护好GitHub Actions工作流,自动运行测试、构建和代码质量检查。
  • 版本管理 :遵循语义化版本控制,定期合并特性分支,发布稳定版本。
  • 社区互动 :在GitHub Issues、Discord或论坛中积极回答用户问题,收集反馈,规划路线图。

生态建设的展望 : 除了连接器,社区还可以在以下方向贡献力量:

  • 可视化组件 :开发更美观、功能更强大的节点组件。
  • 模板市场 :分享预构建的、针对常见场景(如“社交媒体内容同步”、“客户支持工单自动化”)的工作流模板。
  • 监控与告警插件 :开发与Prometheus、Datadog等监控系统深度集成的插件。
  • 企业级特性 :贡献单点登录集成、更细粒度的权限控制、审计日志等功能模块。

开源项目的魅力在于,你创造的不是一个产品,而是一个生态的起点。HarshAI的40天只是一个开始,它的未来,取决于有多少人觉得它有用,并愿意为之添砖加瓦。

更多推荐