本项目 axios 封装见 ../frontend/src/api/client.js
5.1 Network Error / ERR_CONNECTION_REFUSED
原因:后端没起或端口不对。
定位:
- 本项目后端默认 9002,见 ../运行指南.md
- 前端代理目标也是 9002,见 ../frontend/vite.config.ts:13
- 确认后端在跑:
curl http://localhost:9002/health
解决:
cd backend && uv run python run.py --port 9002
5.2 Request failed with status code 401
原因:token 失效 / 没带上。
定位:
- Network 面板看请求头是否有
Authorization: Bearer xxx - 检查 ../frontend/src/api/client.js 拦截器是否正确从 store 取 token
最佳实践:
- 401 在响应拦截器统一处理:清 token、跳登录页、避免每个调用方各自判断
- 不要在每个 API 调用里手动加 token,统一在拦截器里加
5.3 跨域 CORS
症状:浏览器报 Access-Control-Allow-Origin 不允许。
原因:dev 走代理就不应该有跨域。如果出现,八成是 axios baseURL 写了完整域名绕过了代理。
修法:
// ❌ 绕过代理,直接打到 9002,触发 CORS
axios.create({ baseURL: 'http://localhost:9002/api' });
// ✅ 走 Vite 代理
axios.create({ baseURL: '/api' });
生产环境的跨域要后端配 CORS 中间件或 nginx 加响应头。
5.4 文件下载 / 二进制响应被当 JSON 解析
axios 默认 responseType: 'json',下载二进制会乱码。
修法:
axios.get('/api/export.xlsx', { responseType: 'blob' })
.then(res => {
const url = URL.createObjectURL(res.data);
const a = document.createElement('a');
a.href = url;
a.download = 'export.xlsx';
a.click();
URL.revokeObjectURL(url);
});
5.5 上传文件 Content-Type 错误
误区:手动设 Content-Type: multipart/form-data,漏掉 boundary,后端解析失败。
修法:
// ✅ 让浏览器自动加 boundary
const fd = new FormData();
fd.append('file', file);
axios.post('/api/upload', fd); // 不要手动设 Content-Type
5.6 axios 超时:timeout of 0ms exceeded
默认 axios 不超时,长时间挂起。建议:
axios.create({ timeout: 30000 });
但流式响应、大文件上传要单独跳过超时。
5.7 并发请求竞态:后发的先返回,UI 显示旧数据
场景:搜索框快速输入,后请求被先请求覆盖。
修法:
- 用 AbortController 取消旧请求:
const ctrl = new AbortController(); axios.get('/api/search', { signal: ctrl.signal }); // 下次发请求前 ctrl.abort() - 用请求 id 比对,只取最新
5.8 SSE / 长轮询断连
现象:EventSource 连接 30 秒后断开。
原因:nginx / 代理服务器默认空闲超时 30-60 秒。
修法:
- nginx 加
proxy_read_timeout 3600s; proxy_buffering off; - 前端 onerror 里自动重连
- 服务端定期发心跳事件保活
5.9 WebSocket 断连重连
见 09-Yjs协同.md。
5.10 请求被浏览器扩展拦截
某些广告拦截器 / 隐私扩展会拦截带 track、analytics 的 URL,导致 ERR_BLOCKED_BY_CLIENT。
定位:隐身模式复现一次,排除扩展问题。