1. fetch('/api/students') 返回 404,先别动 Spring Boot
前端项目跑得好好的,页面加载没问题,路由也正常,唯独列表页一直空着。F12 打开一开,fetch('/api/students')返回 404,或者直接ERR_CONNECTION_REFUSED。切到后端一看,http://127.0.0.1:8080/api/students用 Postman 打得通,日志里也有正常返回。这种「后端明明活着、前端就是拿不到」的场面,问题十有八九不在 Java 代码里,而在vite.config.ts的server.proxy那几行配置上。
如果你已经被官方模型的额度、多把 Key 来回切、切模型要重开工具这些事卡住过,可以让走 TaoToken 的 Codex 来当这次排查的「读代码搭子」:先打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content= 创建一把 Key,再把 Codex 的 Base URL 填成https://taotoken.net/api,然后让它逐行对照你的vite.config.ts看 proxy 的target有没有写对、changeOrigin有没有漏。注意分工:Codex 负责读文件、解释链路、给修改建议,真正重启 dev server、发/api请求、贴报错回对话的,还是你在自己机器上做。
1.1 一次 /api 请求其实走了三段路
先把链路捋直,不然改配置全靠猜。假设前端 dev server 监听5173,后端 Spring Boot 监听8080,页面里写的是:
const res = await fetch('/api/students') const data = await res.json()这段代码看着像在「调后端」,实际上浏览器不这么认为。/api/students是相对路径,浏览器会把它拼成http://localhost:5173/api/students,请求先打到 Vite 自己的 dev server 上。这是第一段。
第二段在 Vite 内部。dev server 收到请求后,拿 URL 前缀去匹配server.proxy里的 key。匹配上/api,就把请求转发给配置的target,也就是http://127.0.0.1:8080,拼成http://127.0.0.1:8080/api/students。这一段是 Node 侧发出去的,跟浏览器没关系,所以也不受同源策略约束。
第三段才轮到 Spring Boot。请求进到DispatcherServlet,由@RequestMapping("/api/students")的 Controller 接住,返回 JSON,再原路退回 5173,最后回到浏览器的res。
三段里任何一段断了,前端看到的都是一句话「请求失败」,但报错长得很不一样:404 通常说明请求确实到了后端,只是路径对不上;502 或ECONNREFUSED说明 Vite 根本没连上 8080;CORS 报错说明你压根没走 proxy,是直接打 8080 了。先把这三种情况分开,再决定改哪儿。
1.2 把 vite.config.ts 摊开,只看 server.proxy 这一段
一份最典型、也最容易出错的配置长这样:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 5173, proxy: { '/api': { target: 'http://127.0.0.1:8080', changeOrigin: true, // 后端 Controller 本身就带 /api 前缀时,这行千万别写 // rewrite: (path) => path.replace(/^\/api/, ''), }, }, }, })真正影响转发生死的字段只有三个:target、changeOrigin、rewrite。target决定往哪台机器哪个端口发;changeOrigin决定请求头里的Host是否被改写成目标地址的 Host;rewrite决定路径前缀要不要被剥掉。其余像secure、ws、configure都是配套选项,正常本地开发用不到。
很多人第一眼会以为changeOrigin是「开启跨域」的开关,这是最容易踩的误解。它改的是请求头,不是路径,也不会让浏览器放行任何东西——因为请求压根不是浏览器发出去的,是 Vite 的 Node 进程发的。把它理解成「转发时伪装成目标主机」,比理解成跨域开关要准确得多。
2. target / changeOrigin / rewrite 三个字段的典型写错方式
配置报错的特点是可复现:写错哪个字段,症状基本固定。与其反复猜,不如把每种写法的症状先记下来,排查时直接对号入座。
2.1 target 少了协议头、端口对不上、IP 写错
target必须是带协议头的完整 origin,http://127.0.0.1:8080是合法的,127.0.0.1:8080、localhost:8080(省略协议头时)在某些版本会直接抛配置解析错误,http://localhost:8080/末尾多一根斜杠也可能让转发路径变成双斜杠。这些都不是玄学,是 URL 拼接规则决定的。
端口写错是最常见的一种。后端实际跑在8081,配置里还留着8080,症状是浏览器控制台报502 Bad Gateway或者ECONNREFUSED 127.0.0.1:8080,Vite 终端里会打印一行http proxy error。这时候去后端启动日志确认端口,比盯着前端代码看半小时有用。
还有一种更隐蔽的:后端起了两个实例,一个是老版本跑在 8080,一个是新代码跑在 8081。请求能通,返回的数据却是旧的,你会以为接口逻辑没生效。这类问题只能靠curl分别打两个端口比对返回内容来发现。
2.2 changeOrigin 漏写与 rewrite 多写
漏changeOrigin的典型症状是后端能收到请求,但返回结果不对:某些框架会按Host头做虚拟主机路由,或者做了域名白名单校验,看到Host: localhost:5173就把请求打回去了。表现是接口返回 403 或者干脆返回一份默认页面,而不是 404。
rewrite则相反,写多了出问题。如果后端 Controller 的映射本身就是@RequestMapping("/api/students"),你在 proxy 里再把/api前缀剥掉,转发出去的就成了http://127.0.0.1:8080/students,后端找不到这个映射,返回 404。这也是「后端明明能通、前端一直 404」最常见的成因之一。
判断要不要写rewrite,方法很土但很准:在浏览器地址栏直接打开http://127.0.0.1:8080/api/students,能返回 JSON 就说明后端带/api,rewrite不要写;如果返回 404 而http://127.0.0.1:8080/students能通,那才需要剥前缀。
3. 让走 TaoToken 的 Codex 来读这份 vite.config.ts
配置项就这几个,但一个项目里可能同时存在vite.config.ts、.env.development、src/utils/request.ts三处都在定义 baseURL,改一处不生效是常有的事。这时候让 Codex 帮你把所有相关文件一次性读完、列出「哪些地方定义了请求前缀」,比人肉翻文件快得多。
3.1 先去官网建 Key、在模型广场确认模型 ID
这一步跟原文里「注册账号、复制密钥」对应,只是入口统一到 TaoToken。打开 TaoToken 完成注册登录,进控制台创建一把 API Key,复制出来备用。Key 只在创建时完整展示一次,记得先存到本地的密码管理器里,别直接贴进会提交到 Git 的文件。
模型 ID 不要凭记忆写。不同套餐、不同时间上架的模型不一样,正确做法是打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content= 的模型广场,看当时列表里的实际 ID,原样复制。写一个看起来很像的字符串,最大的后果是 Codex 启动就报模型不存在,排查方向还容易被带偏。
3.2 ~/.codex/config.toml 里换成自定义供应商
Codex 的配置文件是~/.codex/config.toml,不是 Claude Code 那套ANTHROPIC_*环境变量,两边的字段名完全不通用,别互相抄。要接 TaoToken 的兼容通道,写法大致如下:
model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"base_url就是接口地址,末尾不要带/v1,填https://taotoken.net/api即可。env_key指的是从哪个环境变量读 Key,所以还要在 shell 里导出:
export TAOTOKEN_API_KEY=YOUR_API_KEYYOUR_MODEL_ID和YOUR_API_KEY都从上面那个控制台页面拿。保存后重开一个终端,让环境变量生效,再启动 Codex。
有一点要提前说清楚:Codex 在这里的角色是「读你本地的vite.config.ts和相关请求封装文件,解释转发链路,提示哪一行可能写错」。它不会、也不应该去连你的后端服务或者执行任何业务操作。真正的/api请求由你本地发起,TaoToken 只负责给 Codex 供 Key。
4. Codex 会指出哪几种可能,哪部分必须你自己验
把vite.config.ts、package.json、以及前端封装请求的那个文件一起丢进对话,问一句「我的/api/students一直 404,帮我看看 proxy 配置哪里可能有问题」,通常能得到几类具体判断。但判断终归是判断,下面这些动作得你自己在本地做。
4.1 先 curl 后端,确认第三段是活的
绕过前端,直接在终端打后端:
curl -i http://127.0.0.1:8080/api/students看状态码和响应体。返回 200 加 JSON,说明后端和路径都没问题,问题在 Vite 这一段;返回 404,说明后端映射路径不对,改 proxy 也是白改;报Connection refused,说明后端没起在 8080,先去确认端口。把这条 curl 的完整输出贴回对话,比描述十句话都管用。
4.2 浏览器 Network 里看请求到底落在哪个端口
打开 F12 的 Network 面板,刷新页面,找到那条/api/students的请求,看两处:Request URL是不是http://localhost:5173/api/students,Status Code是多少。
如果Request URL直接是http://127.0.0.1:8080/api/students,说明你某处写了完整的后端地址,压根没走 proxy,那 CORS 报错就是必然的。如果 URL 对、状态码 404,把 Response 内容(可能是 Spring 的默认错误 JSON)贴回对话,让 Codex 对比后端映射路径。如果状态码是 502,去 Vite 终端看代理错误日志,通常是 target 端口不对。
顺手再看一眼有没有别的地方也在定义请求前缀:.env.development里的VITE_API_BASE_URL、axios.create({ baseURL })、以及某些工具函数的拼接逻辑,都可能让/api变成/api/api或者空字符串。
5. proxy 不生效的五步排查清单
上面零散提到了几种情况,这里收拢成一套可以照着走的顺序。原则是:从浏览器往后端走,每一步只验证一件事,不要跳步。
5.1 从 5173 到 8080 逐段确认
第一步,确认server.proxy的 key 和前端请求的前缀完全一致。key 写/api,请求写/apis,或者大小写不一致,都不会命中。
第二步,确认target的协议、IP、端口三者都对,且不带多余路径。本地后端用http://127.0.0.1:8080或http://localhost:8080都行,但如果后端只在 IPv4 上监听,localhost解析成::1时会连不上,这时候改用127.0.0.1更稳。
第三步,判断要不要changeOrigin。后端有 Host 校验就加上,没有也不影响。
第四步,判断要不要rewrite。用浏览器直接访问后端路径来验证,别靠猜。
第五步,确认改完配置后 dev server 真的重启了。vite.config.ts属于启动期读取的配置,热更新不会重新加载它,必须Ctrl+C停掉再npm run dev。这一步最容易被忽略,很多人改完发现「没生效」,其实只是进程还挂着旧配置。
5.2 配置改对了但 Codex 那边连不上怎么办
排查前端的同时,可能 Codex 侧也会冒出问题。两种常见情况:一种是启动即报 401,多半是env_key指定的环境变量没导出,或者 Key 复制时多带了空格;另一种是报路径不存在一类的 404,检查base_url是不是被写成了带/v1的地址,以及wire_api与当前模型是否匹配,不匹配时把wire_api从responses调成chat再试。
这两种跟 Vite 的 proxy 没有关系,别混在一起排查。一个判断标准:报错发生在 Codex 启动阶段,就是 Key 和接口配置的事;报错发生在浏览器请求阶段,才是vite.config.ts的事。
6. 跑通之后:用同一把 Key 复测一次,再决定下一步
前端能拿到数据、Codex 也能正常回话之后,建议做一次交叉验证,确认整套链路是干净可复现的,而不是靠某次偶然的缓存。
6.1 模型 ID 与 Base URL 的双向核对
在 TaoToken 模型对话 里用同一把 Key 发一条测试消息,确认模型 ID 和接口地址没填错。如果在 Codex 里能用、在对话里报错,问题一般出在环境变量作用域,比如换了终端窗口没重新 export。
6.2 回控制台看这次调用,再决定要不要换套餐
打开 控制台 API Keys 看这次对话有没有被记上账、用量是否正常。如果接下来要长期用 Codex 读项目、改配置,可以顺手看一眼 Coding Plan 的额度是否够用;需要对照字段细节的话,参考文档放在 Claude Code 接入文档,环境变量和自定义供应商的写法可以互相参照,但 Codex 那边记住用的是config.toml,不要套ANTHROPIC_*。
最后留一句提醒:vite.config.ts的 proxy 只服务于本地开发,打包上线后这套转发不复存在,生产环境的接口地址该由 Nginx 或者网关来管。别把 dev 配置当成上线方案,也别让 Codex 顺着手感把生产地址填进去。