feat(teacher-bff): admin 命名空间 + 5 个 gRPC client + health probes + merge-resolvers + nextstep 文档

This commit is contained in:
SpecialX
2026-07-14 16:00:46 +08:00
parent 7b790f1276
commit 895a060491
50 changed files with 9739 additions and 334 deletions

View File

@@ -0,0 +1,64 @@
// SSE 端点P5ai.StreamChat gRPC stream → BFF → 前端 EventSource
// 路由GET /ai/chat/stream
// 裁决依据02-architecture-design.md §10
import { Controller, Get, Query, Res, Req } from "@nestjs/common";
import type { Request, Response } from "express";
import { TeacherService } from "./teacher.service.js";
import type { CallContext } from "../clients/types.js";
import type { ChatMessage } from "../clients/ai/ai.types.js";
@Controller("/ai/chat")
export class AiChatSseController {
constructor(private readonly teacherService: TeacherService) {}
/**
* SSE 流式聊天端点
* 前端使用 EventSource 连接BFF 透传 ai.StreamChat gRPC stream
*/
@Get("stream")
async streamChat(
@Req() req: Request,
@Res() res: Response,
@Query("prompt") prompt: string,
): Promise<void> {
// 设置 SSE 响应头
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.setHeader("X-Accel-Buffering", "no"); // 禁用 Nginx 缓冲
// 构造 CallContext从 req.headers 提取 x-user-id 和 x-request-id
const ctx: CallContext = {
userId: (req.headers["x-user-id"] as string) ?? "dev-user",
traceId: (req.headers["x-request-id"] as string) ?? "",
};
// 构造聊天消息
const messages: ChatMessage[] = [{ role: "user", content: prompt }];
try {
const stream = await this.teacherService.streamChat(ctx, messages);
for await (const chunk of stream) {
const data = JSON.stringify({
content: chunk.content,
done: chunk.done,
});
res.write(`data: ${data}\n\n`);
if (chunk.done) {
break;
}
}
res.write("event: done\ndata: {}\n\n");
} catch (err) {
const errorData = JSON.stringify({
error: err instanceof Error ? err.message : "Unknown error",
});
res.write(`event: error\ndata: ${errorData}\n\n`);
} finally {
res.end();
}
}
}