返回首页

Next.js 与 1C Bitrix 集成:异步线索传输

Next.js 16 与 1C Bitrix 的异步集成实用指南。使用 after() 进行后台线索处理,设置重试机制和 PostgreSQL 架构。解决超时和重复问题。

Next.js 和 1C Bitrix:如何无队列和工作者发送线索
Advertisement 728x90

## 从 Next.js 到 1C-Bitrix 的异步线索传输:无需队列和工作进程的模式

通过 REST API 同步提交线索到 CRM 会引发严重问题:用户需要等待外部请求完成才能收到响应,而 1C-Bitrix API 的不稳定性会导致超时和错误。我们在 Next.js 16 上提出了一种解决方案,它消除了队列和后台进程。该实现将线索保存到本地的 PostgreSQL 数据库,立即向用户响应,并通过 after() 处理与 Bitrix 的集成——这是一个在发送 HTTP 响应后进行后台处理的机制。

无队列架构:三层职责分离

核心原则——将操作分离为同步和异步部分。接收表单时:

  • 使用 zod 进行数据验证
  • 保存到 PostgreSQL,bitrix_id=NULL
  • 立即返回 200 OK 响应
  • 通过 after() 在后台发送到 Bitrix

这保证了:

Google AdInline article slot
  • 独立于 Bitrix REST API 的稳定性
  • 不阻塞用户界面
  • 可以通过 SELECT * FROM leads WHERE bitrix_id IS NULL 手动处理未送达的线索

该方法的关键特性是摒弃 Redis/BullMQ。所有操作都融入 Next.js API 路由,使用内置的 after() 进行响应后处理。这降低了基础设施复杂度,并简化了通过 systemd/nginx 在 VPS 上的部署。

Next.js 16 中的 after():技术实现

after() 方法解决了无服务器环境中“发送后不管”的问题。与 Promise.resolve().then() 不同,它确保即使在发送响应后任务也能执行,且不会在操作完成前终止进程。表单处理示例:

export async function POST(request: Request) {
  // ... validatsiya and antidubl
  
  const [lead] = await pgQuery(...);

  after(async () => {
    try {
      const bitrixId = await sendToBitrix24(payload);
      await pgQuery(
        `UPDATE leads SET bitrix_id = $1 WHERE id = $2`,
        [bitrixId, lead.id]
      );
    } catch (error) {
      console.error("[Leads API] Error otpravki in Bitriks:", error);
    }
  });

  return Response.json({ ok: true, id: lead.id });
}

关键细节:

Google AdInline article slot
  • 防重复通过 PostgreSQL 中的 10 分钟窗口实现,而不是通过 Bitrix(CRM 会无警告接受重复项)
  • bitrix_id 存储为可空 text——反映了操作的异步特性
  • 集成错误记录到 journalctl,不会影响用户响应

适用于 1C-Bitrix 的可靠 HTTP 客户端

与不稳定 API 稳定协作的关键参数:

  • MAX_ATTEMPTS = 2——可靠性和负载之间的平衡
  • REQUEST_TIMEOUT_MS = 8000——高于 Bitrix p95 延迟(400-700 ms)
  • RETRY_DELAY_MS = 1500——线性退避,避免指数复杂度

客户端实现包括:

async function bitrixRequest(method: string, payload: Record<string, unknown>) {
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

    try {
      const response = await fetch(url, {
        signal: controller.signal,
        // ...
      });

      const text = await response.text();
      const json = text ? JSON.parse(text) : {};

      if (!response.ok || json.error) {
        throw new Error(json.error_description || `HTTP ${response.status}`);
      }

      return json;
    } catch (error) {
      if (attempt >= MAX_ATTEMPTS) throw error;
      await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
    } finally {
      clearTimeout(timer);
    }
  }
}

三个关键点:

Google AdInline article slot
  • 使用 response.text() 而非 response.json() 进行手动解析——绕过 Bitrix 空响应的错误
  • 处理两种错误:HTTP 状态码和 CRM 内部错误
  • finally 中保证清理定时器——防止内存泄漏

认证特性与数据模式

对于集成,选择传入 webhook 而非 OAuth:

  • 无需依赖 Marketplace 和审批
  • 简单管理 BITRIX_WEBHOOK_URL 环境变量
  • 在 Bitrix 管理面板中配置权限范围

URL 中令牌泄露风险通过规则最小化:日志和代码仅显示 API 方法,而非完整 URL。

leads 表模式:

CREATE TABLE leads (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  phone text NOT NULL,
  source text,
  bitrix_id text,
  created_at timestamptz NOT NULL DEFAULT now()
);

为什么 bitrix_id 是 text 而非 integer?

  • Bitrix 在 JSON 中返回 ID 为字符串
  • 无需类型转换
  • PostgreSQL 中的 UUID 确保幂等性

关键要点

  • 单一真相来源——本地数据库:在联系 Bitrix 之前保存线索,即使 CRM 失败也能确保数据
  • 响应后处理:after() 取代队列,消除基础设施复杂度
  • 错误处理:带超时的重试和 journalctl 日志确保可观测性
  • 数据库级防重复:按电话的 10 分钟窗口防止重复,无论 CRM 如何
  • 安全认证:webhook 需要严格的 URL 日志控制

此模式适用于每分钟最多 50 个请求的项目。对于高负载系统,可通过 SELECT COUNT(*) FROM leads WHERE bitrix_id IS NULL 添加监控,并通过 cron 实现自动重试。在当前实现中,手动错误处理优于自动重试——它降低了临时故障期间线索重复的风险。

— Editorial Team

Advertisement 728x90

继续阅读