“为什么订单没有生成?用户的钱已经扣了!”
那天晚上,我们整个项目组被紧急召回。 服务器日志正常、数据库正常、接口返回正常—— 但订单,就是凭空消失了。
一、事故发生:无法复现的“幽灵订单”
这是一家大型电商项目的促销夜,后端日志显示交易成功,支付平台也确认扣款成功,但部分用户反馈:
“我付了钱,没订单。”
这不是小Bug,每一分钟可能意味着成百上千的损失。
我们一度以为是数据库写入延迟,可所有监控都正常。
“这就像一个信号在路上被吞掉了。” 后端负责人皱着眉说。
二、排查开始:看似完美的接口
我们用Postman反复测试接口,所有请求都返回 200 OK。
日志中每次调用都显示“订单创建成功”。
但奇怪的是:用户实际下单的那批请求,后台完全没有记录。
那意味着——这些请求,根本没到达服务器。
三、关键线索:Charles登场
前端工程师灵光一闪:
“那我们能不能看看,用户那边到底发了什么?”
于是他在自己的电脑上复现支付流程,并打开 Charles抓包工具 监控全程。
第一轮抓包没发现问题——所有请求都完整发送。 第二轮,他在页面反复操作,突然看到一条异常请求:
POST /api/order/create
Status: 0
Error: Connection reset by peer
这一行让所有人都安静了。
四、进一步分析:网络被谁“掐断”了?
我们在Charles中展开这条请求:
- Header完整;
- Body正常;
- 但 响应为空。
前端以为是网络卡顿,但Charles的 Timing图 告诉我们: 请求在“Sending”阶段耗时极长,几乎超时后被客户端主动断开。
于是我们在 Throttle功能 下模拟了用户的网络状况—— 3G / High Latency 模式下,同样的请求再次中断。
真相开始浮出水面:
弱网下,前端支付成功后立即跳转页面, 导致订单请求被提前取消。
五、验证假设:重放与断点调试
为了验证这个推测,我们使用了Charles的两大功能:
1. Repeat(请求重放)
将那条失败的请求重新发送,结果成功生成订单。
2. Breakpoints(断点调试)
在请求发送前设置断点,手动延迟3秒后再继续, 结果仍然成功。
这印证了假设: 问题并非接口或后端逻辑,而是前端提前切断连接。
六、问题根因:过早的页面跳转
最终我们找到关键代码:
axios.post('/api/order/create', params);
window.location.href = '/success';
请求尚未完成,就跳转了页面。 在良好的网络下,一切正常; 但在高延迟环境中,请求被浏览器中断。
七、解决方案与复盘
我们修改代码为异步等待:
await axios.post('/api/order/create', params);
window.location.href = '/success';
并在弱网环境下通过Charles Throttle测试, 验证多次都正常。
随后,我们将该问题归纳为:
| 环节 | 问题 | Charles功能 | 作用 |
|---|---|---|---|
| 前端请求异常 | 页面跳转中断请求 | SSL Proxying + Timeline | 分析请求状态 |
| 弱网模拟 | 复现场景 | Throttle | 复现问题条件 |
| 请求重试验证 | 确认可复现 | Repeat | 验证假设 |
| 调试验证 | 检查响应延迟 | Breakpoints | 控制请求时机 |
八、这次事故的代价与收获
那次促销活动,我们因为Bug造成了近百万订单补偿。 但也正因为这次危机,我们重新审视了调试体系的重要性。
Charles成为团队的“标配工具”:
- 前端用于监控接口调用;
- 测试用它复现问题;
- 后端用它分析请求状态。
我们甚至在内部wiki中写下了这句话:
“调试不靠猜,先抓包再开口。”
推荐学习资源
如果你也想系统学习如何用Charles做问题排查、性能分析、移动端抓包,可以访问 Charles中文网
这里有:
- 安装与配置教程
- HTTPS证书操作指南
- Rewrite与Throttle高级功能讲解
- 多端实战案例与排查思路
一次Bug的代价,是一次团队的成长
那次事故之后,我们再也不敢“盲调”。 每当遇到接口异常,团队第一反应就是:“开Charles。”
因为我们知道,在那片看似混乱的流量里,总能找到真相。