1. 问题现象与背景分析
最近在开发一个基于Vue 3 + Vite + Ant Design Vue的项目时,遇到了一个诡异的前端接口请求问题。具体表现为:当点击弹窗的「确定」按钮后,按钮会一直转圈,弹窗无法关闭,同时Network面板中请求状态显示为pending(待处理),整个页面变得无法交互,就像"卡死"了一样。
更奇怪的是,后端日志显示这些请求根本没有到达服务器。而同一个页面的列表查询接口却完全正常,只有提交类接口(如新增、状态变更等)会出现这个问题。
2. 错误的排查路径与反思
2.1 初期的猜测式排查
一开始,我采用了猜测式的排查方法,这导致浪费了大量时间:
- 猜测"响应体为空":修改了axios拦截器,但问题依旧
- 猜测"后端序列化问题":调整了ObjectMapper配置,仍然无效
- 猜测"全局Loading锁死":移除了SmartLoading组件,部分有效但未根治问题
- 猜测"字段类型不对":修改了locationId字段,虽然有效但请求仍然pending
2.2 反思与教训
这种猜测式的排查方法存在几个严重问题:
- 缺乏系统性:没有先定位问题到底卡在哪一层,导致修改没有针对性
- 效率低下:每次修改都需要重新构建、部署和测试,耗费大量时间
- 可能引入新问题:频繁修改代码可能带来新的bug
3. 正确的排查方法论
3.1 第一层排查:请求是否发出
首先需要确定请求是否真的发出了。在Network面板中观察请求状态:
| 状态 | 含义 | 下一步行动 |
|---|---|---|
| 没有请求出现 | JS代码没执行到API调用 | 在组件中加console.log定位卡在哪一行 |
| pending(灰色) | 请求已发出,等待响应 | 排查网络层(代理/后端) |
| 200/500等 | 请求已完成 | 排查响应处理逻辑(拦截器/组件) |
3.2 第二层排查:代理链路检查
在Vite开发环境下,请求会经过以下链路:浏览器 → Vite Proxy → 后端服务。为了排查问题,可以在vite.config.js中添加代理日志:
javascript复制// vite.config.js
server: {
proxy: {
'/api': {
target: 'http://192.168.1.100:8080',
changeOrigin: true,
configure: (proxy) => {
proxy.on('proxyReq', (_, req) => {
console.log(`[Proxy →] ${req.method} ${req.url}`);
});
proxy.on('proxyRes', (proxyRes, req) => {
console.log(`[Proxy ←] ${proxyRes.statusCode} ${req.url}`);
});
}
}
}
}
这个配置可以帮助我们一次性看清三个环节的情况:
- 浏览器是否发出了请求
- Vite代理是否接收并转发了请求
- 后端是否响应了请求
4. 问题根源与解决方案
4.1 实际发现的问题
通过上述方法排查后,发现问题的根源在于:
- 请求头设置不当:某些提交类接口需要特定的Content-Type,而默认设置不正确
- 跨域问题:虽然查询接口正常,但提交接口因为预检请求(OPTIONS)失败导致阻塞
- 代理配置不完整:部分接口路径没有被正确代理
4.2 具体解决方案
- 统一请求头配置:
javascript复制// axios配置
const instance = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json',
}
});
- 完善代理配置:
javascript复制// vite.config.js
server: {
proxy: {
'/api': {
target: 'http://backend-service',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
// 处理OPTIONS请求
configure: (proxy) => {
proxy.on('proxyReq', (proxyReq) => {
if (proxyReq.method === 'OPTIONS') {
proxyReq.setHeader('Access-Control-Allow-Origin', '*');
proxyReq.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
proxyReq.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
}
});
}
}
}
}
- 后端配合调整:
确保后端服务正确处理OPTIONS请求,返回适当的CORS头信息。
5. 预防措施与最佳实践
5.1 开发环境配置检查清单
-
代理配置验证:
- 确保所有API路径都被正确代理
- 检查代理目标地址是否正确
- 验证changeOrigin设置
-
请求头一致性:
- 统一前端请求头设置
- 确保与后端期望的Content-Type匹配
-
跨域处理:
- 前端正确处理预检请求
- 后端配置适当的CORS策略
5.2 调试技巧
-
使用浏览器开发者工具:
- Network面板过滤XHR请求
- 查看请求/响应头详细信息
- 使用Preserve log选项防止刷新丢失日志
-
服务端日志配合:
- 确保能查看完整的请求链路日志
- 从浏览器到代理再到后端的全链路追踪
-
最小化复现:
- 创建一个最简单的测试用例
- 逐步添加复杂度,定位问题出现点
6. 性能优化建议
6.1 请求优化
- 合并请求:对于频繁的小请求,考虑合并
- 缓存策略:合理使用缓存减少重复请求
- 请求取消:实现请求取消机制,避免pending堆积
6.2 错误处理增强
- 全局错误拦截:
javascript复制instance.interceptors.response.use(
response => response,
error => {
if (error.code === 'ECONNABORTED') {
console.error('请求超时');
}
return Promise.reject(error);
}
);
- 超时设置:
javascript复制const instance = axios.create({
timeout: 10000, // 10秒超时
});
- 重试机制:对于特定错误实现自动重试
7. 项目架构思考
7.1 前后端协作规范
-
接口文档:维护详细的接口文档,包括:
- 请求方法
- 请求头要求
- 参数格式
- 响应格式
-
Mock服务:开发阶段使用Mock服务,避免依赖后端进度
-
契约测试:引入契约测试确保前后端约定不被破坏
7.2 监控与告警
-
前端监控:
- 记录慢请求
- 统计失败率
- 监控pending请求数量
-
异常上报:
- 自动收集错误上下文
- 区分网络错误与业务错误
-
性能指标:
- 记录接口响应时间
- 分析请求瀑布图
8. 扩展知识:网络请求生命周期
理解完整的网络请求生命周期对于排查这类问题很有帮助:
-
浏览器层面:
- DNS解析
- TCP连接
- TLS握手(HTTPS)
- HTTP请求发送
-
代理层面:
- 请求转发
- 头信息修改
- 路径重写
-
服务端层面:
- 路由匹配
- 中间件处理
- 业务逻辑执行
- 响应生成
-
返回路径:
- 代理接收响应
- 浏览器接收响应
- 前端处理响应
9. 工具推荐
9.1 开发调试工具
- Charles/Fiddler:抓包分析工具
- Postman/Insomnia:接口测试工具
- Wireshark:网络协议分析(高级)
9.2 浏览器插件
- React Developer Tools:React组件调试
- Vue Devtools:Vue组件调试
- Redux DevTools:状态管理调试
9.3 线上监控工具
- Sentry:前端错误监控
- Datadog:全链路监控
- New Relic:性能监控
10. 总结与个人体会
这次排查经历让我深刻认识到系统性思考的重要性。在遇到问题时,应该:
- 先观察现象:收集完整的问题表现
- 定位层级:确定问题发生的具体环节
- 最小化复现:创建最简单的测试用例
- 逐步验证:每次只改变一个变量
- 记录过程:保持详细的排查记录
对于前端开发者来说,深入理解网络请求的完整生命周期至关重要。这不仅能帮助我们快速定位问题,还能在架构设计时做出更合理的决策。