Files
zhuangang/topics/backend/nodejs/scenarios/ai-bff.md
T

3.9 KiB
Raw Blame History

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id title type domain stack tags level prerequisites est_hours related week
backend.nodejs.scenario.ai-bff 用 Node.js 给 AI 应用做 BFF:鉴权、限流、流式转发、超时和断开处理 scenario backend
nodejs
redis
nginx
fastapi
BFF
SSE
流式转发
限流
超时
request id
intermediate
backend.nodejs.express-nest
2
backend.nodejs.interview
frontend.ssr.scenario.help-center
14

用 Node.js 给 AI 应用做 BFF:鉴权、限流、流式转发、超时和断开处理

业务背景与问题

前端团队维护的 AI 助手页面直接调用 Python 的 Agent 服务,问题:

  1. Agent 服务要校验各业务线不同的登录态,越来越臃肿;
  2. 页面需要的数据来自 3 个服务,前端串行请求,首屏慢;
  3. 用户关掉页面后,Agent 仍在生成,浪费模型费用;
  4. 出问题时无法串起前端、BFF、Agent 三方日志。

约束

  • 前端团队熟悉 TypeScript,希望自己维护这一层。
  • Agent 服务由 AI 团队维护,接口为 SSE。

方案设计

  1. BFF(Express 5 或 NestJS):统一鉴权(会话 / JWT)、按用户限流(Redis)、生成并透传 x-request-id。
  2. 聚合接口:首屏所需的用户信息、会话列表、配额用 Promise.allSettled 并行请求,各自设超时,部分失败时降级返回。
  3. 流式转发:fetch 上游时传入 AbortSignal,res.on("close") 时中止;首字节超时和总超时分别控制;上游出错时发送 SSE error 事件而不是挂起。
  4. 部署:多实例无状态,Nginx 关闭对 SSE 路由的缓冲,设置较长的读超时。

关键代码

app.post("/chat", async (req, res) => {
  const user = await auth(req);                                   // 失败时 throw HttpError(401)
  await rateLimit(`chat:${user.id}`);                             // 超限 throw HttpError(429)
  const controller = new AbortController();
  res.on("close", () => controller.abort());
  const timeout = setTimeout(() => controller.abort(), 120_000);  // 总超时
  const upstream = await fetch(`${AGENT_URL}/chat`, {
    method: "POST",
    headers: { "content-type": "application/json", "x-request-id": res.locals.requestId, "x-user-id": user.id },
    body: JSON.stringify({ question: req.body.question }),
    signal: controller.signal,
  });
  res.set({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "X-Accel-Buffering": "no" });
  try {
    for await (const chunk of upstream.body!) res.write(chunk);  // 原样透传上游的 SSE 字节
  } catch {
    if (!controller.signal.aborted) res.write('event: error\ndata: {"message":"生成中断"}\n\n');
  } finally {
    clearTimeout(timeout);
    res.end();
  }
});

可运行的基础版本(含测试):examples/express-bff。

取舍

选择 优点 缺点
Node BFF(本方案) 前端团队自治、类型共享、擅长 I/O 多一层服务和一跳网络
Next.js 路由处理器做 BFF 和页面同一个项目 受部署形态限制(Serverless 超时)
由 Agent 服务直接对外 链路短 鉴权、聚合逻辑侵入 AI 服务

易错点

  • 把上游的流读完再返回(await upstream.text()),失去流式效果。
  • 没有向上游传播取消。
  • Promise.all 中一个接口失败导致整页失败:首屏聚合用 allSettled + 降级。
  • 在 BFF 里堆业务逻辑,最后变成“第二个后端”。

面试怎么讲

“AI 助手页面我们加了一层 Node BFF:统一鉴权和按用户限流,生成 request id 透传到 Agent 服务;首屏数据并行聚合、部分失败降级;对话接口把 Agent 的 SSE 原样透传,用户断开时通过 AbortController 中止上游,节省模型费用;上游失败时发 error 事件。BFF 保持薄,只做协议适配和横切逻辑。”