phase 5 part 3: mongodb, elasticsearch hybrid search, data-pipeline, nodejs BFF topics with verified examples

This commit is contained in:
Shikui Wang committed 2026-10-23 16:38:52 +08:00
1 parent 9eeb10aede
commit 075872e31e
27 files changed
+2405 -52

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
---
id: backend.nodejs.scenario.ai-bff
title: 用 Node.js 给 AI 应用做 BFF:鉴权、限流、流式转发、超时和断开处理
type: scenario
domain: backend
stack: [nodejs, redis, nginx, fastapi]
tags: [BFF, SSE, 流式转发, 限流, 超时, request id]
level: intermediate
prerequisites: [backend.nodejs.express-nest]
est_hours: 2
related: [backend.nodejs.interview, frontend.ssr.scenario.help-center]
week: [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 路由的缓冲,设置较长的读超时。
## 关键代码
```ts
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](../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 保持薄,只做协议适配和横切逻辑。”