Skip to content

前端核心服务层

本章目标:

  1. 讲清楚 frontend/src/core 下各服务模块(api/auth/skills/mcp/memory/uploads/settings/models/notification/config)如何统一封装对 Gateway API 的调用,以及为什么需要这一层。
  2. 拆解 React Query(QueryClientProvider)如何承接服务器状态,服务模块如何按 api.ts + hooks.ts + types.ts 三件套组织。
  3. 梳理每个服务模块与具体 Gateway 路由的对应关系,以及 CSRF / 鉴权 / baseURL 的共享约定。

TL;DR

core 服务层把"前端组件如何调用后端"这件事收敛成一套统一约定:所有 REST 请求都经 core/api/fetcher.tsfetch 封装(自动带 cookie、自动注入 X-CSRF-Token、401 自动跳登录),baseURL 由 core/config 统一推导,LangGraph 流式请求走 core/api/api-client.ts 的单例 SDK。每个业务域(skills/mcp/memory/uploads/models)是一个独立目录,内部用 api.ts(纯请求)+ hooks.ts(React Query 包装)+ types.ts(类型)三件套,服务器状态由 @tanstack/react-query 管理,本地偏好(settings)走 localStorage,认证态由 AuthProvider Context 提供。

Overview

为什么前端不直接在组件里 fetch('/api/skills'),而要先过一层 core 服务封装 + React Query?核心原因有四个,都是横切关注点不能散落在每个调用点:

  1. CSRF 与 Cookie 契约必须全局一致。Gateway 的 CSRFMiddleware 对所有状态变更方法(POST/PUT/DELETE/PATCH)强制 Double Submit Cookie 校验,缺失 X-CSRF-Token 头直接返回 403;同时跨源 SSR 路由的请求必须带 credentials: "include" 才能携带 HttpOnly 的 access_token cookie。这两个契约被收敛进 core/api/fetcher.tsfetch,任何用原生 fetch() 的调用点都会静默失败 frontend/src/core/api/fetcher.ts:39-89

  2. baseURL 推导逻辑随部署形态变化。是否配置 NEXT_PUBLIC_BACKEND_BASE_URL、是否 mock、SSR 还是浏览器,baseURL 都不同,统一由 core/configgetBackendBaseURL / getLangGraphBaseURL 计算 frontend/src/core/config/index.ts:11-44

  3. 服务器状态需要缓存、失效与去重。skills、mcp 配置、models 列表、memory 这些数据是后端真理来源,组件多处订阅、写操作后要回刷。React Query 用 queryKey 做缓存与失效,避免每个组件各自轮询 frontend/src/core/skills/hooks.ts:7-31

  4. 认证态需要 SSR 注入避免闪烁AuthProvider 只持有展示用 user,初始值由服务端守卫注入,不在客户端持有任何 JWT frontend/src/core/auth/AuthProvider.tsx:36-50

Architecture

core 服务层与 Gateway 路由(详见第 13 章)一一对应。整体分两条数据通道:

  • REST 通道:业务域目录的 api.ts 调用 core/api/fetcher.tsfetch,经 core/config 拼出 baseURL,打到 Gateway 的 FastAPI 路由(全部 prefix="/api",见后端 router)。
  • 流式通道:LangGraph 会话流不走 REST 封装,而走 core/api/api-client.ts 单例 SDK 客户端,通过 onRequest 钩子注入 CSRF(第 29 章详述消息流)。

服务器状态由 QueryClientProvider(@tanstack/react-query)在 workspace 子树注入,本地偏好(settings)走 useSyncExternalStore + localStorage,与 React Query 互不干扰。

Source 列表:

Components / Subsystems

api(请求基础设施)

职责:所有 REST 请求的统一入口与 LangGraph SDK 单例。

  • fetcher.ts 导出 fetch:对状态变更方法注入 X-CSRF-Token(从 csrf_token cookie 读取,不覆盖调用方已设置的同名头),强制 credentials: "include",401 时跳 /login 并抛错 frontend/src/core/api/fetcher.ts:56-89readCsrfCookie 是 SSR 安全的(document 未定义时返回 null)frontend/src/core/api/fetcher.ts:29-37
  • api-client.tsgetAPIClient() 返回按 mock/default 缓存的 LangGraphClient 单例,onRequest 钩子 injectCsrfHeader 每次请求实时读 cookie(处理登录/登出/改密的 cookie 轮转),并对 runs.stream/runs.joinStream 包一层 sanitizeRunStreamOptions 过滤不支持的 streamMode frontend/src/core/api/api-client.ts:42-72
  • feedback.ts 提供 upsertFeedback/deleteFeedback,对接 PUT|DELETE /api/threads/{id}/runs/{runId}/feedback frontend/src/core/api/feedback.ts:11-42

对接 Gateway:feedback 路由 prefix="/api/threads" backend/app/gateway/routers/feedback.py:19

auth(认证态)

职责:在客户端只持有展示用 user,从不持有 JWT;SSR 侧用 cookie 拉取用户。

  • AuthProvider.tsx:Context 提供 user / isAuthenticated / isLoading / logout / refreshUser,refreshUser/api/v1/auth/me,logoutPOST /api/v1/auth/logout 并立即清本地态防闪烁;tab 重新可见时节流(60s)刷新用户 frontend/src/core/auth/AuthProvider.tsx:56-123
  • server.ts:getServerSideUser() 在 SSR 用 access_token cookie 调内部 Gateway /api/v1/auth/me/api/v1/auth/setup-status,返回标签联合 AuthResult(authenticated / needs_setup / system_setup_required / unauthenticated / gateway_unavailable / config_error),5s 超时 frontend/src/core/auth/server.ts:12-96
  • gateway-config.ts:从 DEER_FLOW_INTERNAL_GATEWAY_BASE_URL 读内部网关地址(默认 http://127.0.0.1:8001),Zod 校验后缓存 frontend/src/core/auth/gateway-config.ts:12-31
  • types.ts:userSchemabuildLoginUrlparseAuthError(解包 FastAPI {detail:{code,message}} 多种形态)frontend/src/core/auth/types.ts:63-94

对接 Gateway:/api/v1/auth/*(详见第 14 章鉴权)。

skills(技能管理)

职责:列出 / 启停技能,安装技能。

  • api.ts:loadSkills(GET /api/skills,取 json.skills)、enableSkill(PUT /api/skills/{name} body {enabled})、installSkill(POST /api/skills/install,失败时把 detail 转成统一 {success:false,...})frontend/src/core/skills/api.ts:6-63
  • hooks.ts:useSkills(queryKey ["skills"])、useEnableSkill(mutation 成功后 invalidateQueries(["skills"]))frontend/src/core/skills/hooks.ts:7-31

对接 Gateway:skills 路由 prefix="/api",含 GET/POST /skillsPUT /skills/{name}backend/app/gateway/routers/skills.py:21-103

mcp(MCP 配置)

职责:读 / 写 MCP 服务器配置,启停单个 server。

对接 Gateway:mcp 路由 prefix="/api" backend/app/gateway/routers/mcp.py:12

memory(长期记忆)

职责:读取 / 清空 / 导入导出用户记忆,facts 的增删改。

  • api.ts:loadMemory(GET /api/memory)、clearMemory(DELETE)、deleteMemoryFact(DELETE /api/memory/facts/{id})、exportMemoryimportMemory(POST /api/memory/import)、createMemoryFact(POST /api/memory/facts)、updateMemoryFact(PATCH);统一用 readMemoryResponse 把后端 detail(字符串/数组/对象)归一为错误信息 frontend/src/core/memory/api.ts:10-149
  • hooks.ts:useMemory(queryKey ["memory"]),所有写操作 mutation 成功后用 queryClient.setQueryData(["memory"], memory) 直接以响应体回写缓存(后端返回完整新状态,免回刷)frontend/src/core/memory/hooks.ts:17-84

对接 Gateway:memory 路由 prefix="/api" backend/app/gateway/routers/memory.py:18。前端还有一个 Next.js route handler app/api/memory/route.ts 做服务端透传代理(用 NEXT_PUBLIC_BACKEND_BASE_URL 默认 http://127.0.0.1:8001)frontend/src/app/api/memory/route.ts:3-31

uploads(文件上传)

职责:线程级文件上传 / 列出 / 删除。

  • api.ts:uploadFiles(POST FormData/api/threads/{id}/uploads)、listUploadedFiles(GET .../uploads/list)、deleteUploadedFile(DELETE .../uploads/{filename}),失败时读 detail frontend/src/core/uploads/api.ts:44-107
  • hooks.ts:useUploadFiles / useDeleteUploadedFile 成功后失效 ["uploads","list",threadId];useUploadedFiles 依赖 enabled: !!threadId;useUploadFilesOnSubmit 把上传封装成提交流程里的回调 frontend/src/core/uploads/hooks.ts:19-79

对接 Gateway:uploads 路由 prefix="/api/threads/{thread_id}/uploads" backend/app/gateway/routers/uploads.py:34(文件上传与文档转换详见第 27 章)。

settings(本地偏好)

职责:浏览器侧用户偏好(通知开关、token 用量显示、线程级模型覆盖),不走后端。

对接 Gateway:无(纯 localStorage,DEFAULT_LOCAL_SETTINGSfrontend/src/core/settings/local.ts:4-20)。

models / notification

Data Flow

以"用户在设置面板启停一个技能"为例,展示组件 → core hook → React Query → fetch → Gateway → 缓存回写的完整链路。

下图给出 baseURL 与 CSRF 在 REST / 流式两条通道的分流决策:

速查表

core 服务模块 × 对应 Gateway 路由矩阵(Gateway 路由的 prefix 在后端 router,前端经 getBackendBaseURL() 拼接):

core 模块关键 hook / 函数HTTP 方法 + 路径React Query queryKeySource
api/feedbackupsertFeedback / deleteFeedbackPUT/DELETE /api/threads/{id}/runs/{runId}/feedback—(直接调用)前端 / 后端
api/api-clientgetAPIClient()LangGraph SDK → /api/langgraph/*—(SDK 内部)前端
authuseAuth / getServerSideUserGET /api/v1/auth/me,POST /api/v1/auth/logout—(Context)前端 / 后端
skillsuseSkills / useEnableSkillGET/POST /api/skills,PUT /api/skills/{name}["skills"]前端 / 后端
mcpuseMCPConfig / useEnableMCPServerGET/PUT /api/mcp/config["mcpConfig"]前端 / 后端
memoryuseMemory / useCreateMemoryFactGET/DELETE /api/memory,POST/PATCH /api/memory/facts["memory"]前端 / 后端
uploadsuseUploadFiles / useUploadedFilesPOST/GET/DELETE /api/threads/{id}/uploads*["uploads","list",threadId]前端 / 后端
modelsuseModelsGET /api/models["models"]前端 / 后端
settingsuseLocalSettings / useThreadSettings—(localStorage)—(useSyncExternalStore)前端
notificationuseNotification—(浏览器 Notification API)前端
configgetBackendBaseURL / getLangGraphBaseURL—(baseURL 推导)前端

Common Pitfalls / Tips

References

章节关系
./13-Gateway-API与路由体系.md本章服务层对接的后端路由全貌,prefix 与 router 定义在此
./14-鉴权-CSRF与授权.mdfetcher.ts 的 CSRF Double Submit 与 401 跳转对应的后端鉴权机制
./28-前端技术栈与应用结构.mdcore 在 src/ 中的定位、env 校验与整体应用结构背景
./29-AI消息流与流式渲染.mdapi-client.ts 单例提供的 LangGraph 流式通道在此详述(本章不重复)
./30-工作区与聊天界面.mdQueryClientProvider 注入点 workspace 子树、threads/todos/artifacts 由该章覆盖

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析