// 不自动运行:需要先 npm install(express 5)。Node 23.6+ 可以直接运行 .ts(类型擦除) // Express 5 BFF 示例:请求 id、输入校验、统一错误处理、async 路由(Express 5 会自动捕获 Promise 拒绝)、SSE 流式转发 import express, { type NextFunction, type Request, type Response } from "express"; import { randomUUID } from "node:crypto"; export class HttpError extends Error { status: number; code: string; constructor(status: number, code: string, message: string) { super(message); this.status = status; this.code = code; } } export type Deps = { // 上游 AI 服务:返回逐段文本的异步迭代器;真实项目里是对 Python 服务的 fetch 流式调用 askLLM: (question: string, signal: AbortSignal) => AsyncIterable; findOrder: (id: string) => Promise<{ id: string; status: string } | null>; }; export function createApp(deps: Deps) { const app = express(); app.use(express.json({ limit: "100kb" })); // 限制请求体大小 app.use((req, res, next) => { // 请求 id:贯穿日志和上游调用,便于排查 const id = req.get("x-request-id") ?? randomUUID(); res.locals.requestId = id; res.set("x-request-id", id); next(); }); app.get("/healthz", (_req, res) => { res.json({ ok: true }); }); // Express 5:async 处理函数抛出的错误会自动交给错误处理中间件(Express 4 需要手动 next(err)) app.get("/orders/:id", async (req, res) => { const id = req.params.id; if (!/^[A-Z]\d{3,}$/.test(id)) throw new HttpError(400, "BAD_ORDER_ID", "订单号格式不正确"); const order = await deps.findOrder(id); if (!order) throw new HttpError(404, "ORDER_NOT_FOUND", "订单不存在"); res.json(order); }); app.post("/chat", async (req, res) => { const question: unknown = req.body?.question; if (typeof question !== "string" || question.length === 0 || question.length > 500) { throw new HttpError(400, "BAD_QUESTION", "question 必须是 1~500 字的字符串"); } const controller = new AbortController(); res.on("close", () => controller.abort()); // 客户端断开:通知上游停止生成 res.set({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "X-Accel-Buffering": "no" }); res.flushHeaders(); try { for await (const token of deps.askLLM(question, controller.signal)) { if (controller.signal.aborted) break; res.write(`data: ${JSON.stringify({ token })}\n\n`); } res.write("event: done\ndata: {}\n\n"); } catch (e) { // 响应头已经发出,不能再改状态码:用一个 error 事件告诉前端 res.write(`event: error\ndata: ${JSON.stringify({ message: "生成失败,请重试" })}\n\n`); console.error(res.locals.requestId, e); } finally { res.end(); } }); app.use((_req, _res, next) => next(new HttpError(404, "NOT_FOUND", "接口不存在"))); // 统一错误处理:4 个参数。不把内部错误细节返回给客户端 app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => { if (err instanceof HttpError) { res.status(err.status).json({ code: err.code, message: err.message, requestId: res.locals.requestId }); return; } const status = (err as { status?: number }).status; if (status === 400 || status === 413) { // express.json 的解析错误 / 请求体过大 res.status(status).json({ code: "BAD_REQUEST", message: "请求体无效", requestId: res.locals.requestId }); return; } console.error(res.locals.requestId, err); res.status(500).json({ code: "INTERNAL", message: "服务器内部错误", requestId: res.locals.requestId }); }); return app; }