Skip to main content
整个前端只跟一个配置好的地址通信——网关。apps/web/src/lib/api/httpClient.ts 暴露两个 Axios 实例,它们都指向这同一个源、共享同一个鉴权拦截器。所有网络调用都走它们——不要直接 fetch,不要为某个功能再 axios.create() 一个。

单一源

NEXT_PUBLIC_API_URL唯一的后端变量,浏览器端和服务端用同一套解析——没有按服务拆分的历史回退。它指向网关apps/gateway,dev 里 :3110),网关按路径前缀转发到对应上游(platform-api 还是 agent-service)。前端从不直接寻址某个具体服务。
API_ORIGIN 放在无依赖的 routes.ts(而非 httpClient.ts),这样 client 和 server 都能 import 它、又不会顺带把 Axios 拉进来。

两个实例,同一个源

两者都是 createClient(API_ORIGIN)——同一个 base URL。拆成两个纯粹是为了调用侧语义(CRUD 和 AI 分开读起来更清楚),并不是两个后端。每条 /api/... 具体去哪,由网关决定。

鉴权拦截器

configureHttpClient(getter) 在两个客户端上注册同一个请求拦截器:
拦截器调用 getAuthToken(),拿到 token 就设 Authorization: Bearer <jwt>(除非调用方已自带)。getAuthToken() 同时缓存最近一次有效 token;getCachedAuthToken() 把它同步暴露出来,供退出时的 keepalive 请求用(见 resumeApi.syncResumeKeepalive)——那种场景页面随时会死,等不起异步取 token。
功能代码里不要手动传 token。如果你在写 httpClient.api.get(url, { headers: { Authorization: ... } }),说明拦截器在这个上下文没接好——去修接线,别绕过它。

集中存路由

所有路径放在 src/lib/api/routes.ts,且都在 /api/* 前缀下(网关的命名空间):
规则:
  1. 不允许内联路径字符串——每个后端路径都进这几张表。
  2. API_ROUTES / AGENT_ROUTES 打到网关;WEB_AGENT_ROUTES 是 web 应用自己的 Next.js 路由处理器(app/api/chat-agent/*),再由它代理到 agent。
这让”后端改路由”从”全仓 grep + 祈祷”变成一份单文件改动。

加一个新接口

  1. 把路径加到 routes.ts 里对应的表。
  2. src/lib/api/<feature>.ts 里加一个带类型的封装,返回类型化响应。
  3. httpClient.apihttpClient.agent——不要再 axios.create() 第三个客户端。
  4. 如果是云端专属功能,在调用侧用 APP_MODE === 'cloud' 做开关。