1. 这不是“造数据”,而是前端联调的呼吸系统
你有没有遇到过这样的场景:后端接口还没交付,UI设计稿已经堆满钉钉消息,产品经理在群里@所有人问“页面什么时候能跑起来”;或者更糟——接口返回格式突然变了,字段名从user_name改成userName,前端所有页面集体报错,而你翻遍文档也找不到最新契约;又或者,测试环境里某个支付回调总是500,但日志里只有一行“内部错误”,连具体哪一行抛异常都看不到。这时候,Mock 不是锦上添花的玩具,它是你每天能正常呼吸的氧气。
我做前端开发和联调支持十年,带过二十多个中大型项目,从金融风控系统到电商秒杀平台,最深的体会是:真正的联调瓶颈,从来不在代码写得对不对,而在于“数据流是否可控、可追溯、可复现”。Mock 接口数据实操,表面看是伪造返回值,背后是一整套数据治理逻辑——规则改写解决的是“数据契约不一致”的问题,断点拦截解决的是“请求路径不可见、不可干预”的问题,而联调本身,是把这两者拧成一股绳,让前后端在同一个数据语境里对话。
标题里的“规则改写”和“断点拦截”,不是两个并列功能,而是分属不同层级的控制能力:规则改写作用于响应体(Response Body),它决定“我给你什么数据”;断点拦截作用于请求链路(Request Flow),它决定“我让你发给谁、什么时候发、发完之后我怎么插手”。两者叠加,才构成完整的“中间人”能力——你既不是纯客户端,也不是纯服务端,而是站在流量必经之路上的那个调度员。
这个实操方案,适合三类人:一是刚接手老项目的前端,面对一堆“祖传接口”不知从何下手;二是需要快速验证 UI 交互逻辑的产品/设计师,不想等后端排期;三是测试工程师,需要构造边界值、异常状态来压测业务流程。它不依赖后端配合,不修改任何生产代码,所有操作都在本地或测试环境完成,但效果直逼真实联调。接下来,我会用一个真实金融类项目(模拟股票行情+用户持仓查询)贯穿全文,把每一步背后的“为什么这么选”“踩过什么坑”“参数怎么算”全盘托出。
2. 整体设计思路:为什么不用 Axios Mock Adapter 或 Vite Mock Server?
先说结论:Axios Mock Adapter 适合单元测试,Vite Mock Server 适合开发阶段静态模拟,但都不适合真实联调场景。这不是技术优劣问题,而是定位错位。
我试过在三个项目里强行用 Axios Mock Adapter 做联调:第一个项目,前端用 Vue3 + TypeScript,后端是 Java Spring Boot,约定接口前缀/api/v1/。当时觉得 Mock Adapter 简单,直接在main.ts里全局注册:
import axios from 'axios'; import { mockAdapter } from './mock/adapter'; mockAdapter(axios); // 注册拦截器结果联调第一天就崩了——后端同事临时加了个/api/v1/stock/tick?symbol=600519的实时行情接口,但 Mock Adapter 只能按 URL 字符串匹配,而真实请求带了动态 query 参数。我不得不写一堆正则去捕获symbol=后面的值,再手动拼 JSON 返回。更麻烦的是,当后端改了响应结构(比如把data字段从对象改成数组),Mock 文件要同步改,但没人通知我,导致页面白屏两小时,最后发现是 Mock 数据里少了一个list包裹层。
Vite Mock Server 看似更智能,它基于文件系统自动映射路由。比如建个mock/stock.ts:
export default [ { url: '/api/v1/stock/tick', method: 'get', response: ({ query }) => { const symbol = query.symbol; return { code: 0, data: { symbol, price: 1823.5, change: -0.23 } }; } } ]但它有个致命缺陷:所有 Mock 规则必须提前写死,无法在运行时动态修改。联调中期,测试发现一个关键 bug:当用户持仓为空时,后端返回data: null,但前端组件没做空值判断直接.map()报错。我想立刻模拟这个null场景,但 Vite Mock Server 需要重启服务才能生效,而此时后端正在紧急修复线上问题,我连npm run dev都不敢敲——怕打断其他同事的调试。
所以最终我们选了Charles Proxy + 自定义 Map Local + Breakpoint 拦截组合方案。理由很实在:
- Charles 是 HTTP/HTTPS 流量代理,它工作在 TCP 层之上、应用层之下,所有浏览器、App、小程序发出的请求都必须经过它,不依赖前端框架、不侵入业务代码、不改变构建流程;
- Map Local 功能允许你把任意远程 URL 映射到本地 JSON 文件,实现“规则改写”——比如把
https://prod-api.example.com/api/v1/position映射到./mock/position-empty.json,返回空数据; - Breakpoint 拦截则让你在请求发出前、响应返回前各设一个断点,像手术刀一样精准切开流量,在任意环节注入、修改、阻断数据——这才是“断点拦截”的真意,不是简单开关,而是实时干预。
有人会问:为什么不用 Fiddler 或 mitmproxy?Fiddler 在 macOS 上兼容性差,mitmproxy 命令行操作门槛高,而 Charles 的 GUI 对前端极其友好,断点界面直观到连实习生都能上手。更重要的是,它的 SSL Proxying 设置一次,后续所有 HTTPS 请求自动解密,省去证书安装的无数坑——这点在金融类项目里尤其关键,因为所有接口都强制 HTTPS,且证书校验严格。
3. 核心细节解析:规则改写与断点拦截的底层逻辑
3.1 规则改写:Map Local 不是“替换URL”,而是“重定向响应源”
很多人把 Charles 的 Map Local 理解成简单的 URL 替换,比如“把 A 请求换成 B 请求”。这是误区。Map Local 的本质是HTTP 响应体劫持(Response Body Hijacking):它监听所有匹配规则的请求,当请求到达 Charles 时,Charles 并不转发给原始服务器,而是直接读取你指定的本地文件,把文件内容作为 HTTP 响应返回给客户端。
这意味着:请求头(Headers)、状态码(Status Code)、响应头(Response Headers)全部由你本地文件控制,而不受原始服务器影响。这正是规则改写的威力所在。
举个金融项目的真实例子:后端有一个获取用户持仓的接口GET /api/v1/position,生产环境返回:
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 X-Request-ID: abc123 { "code": 0, "msg": "success", "data": [ { "symbol": "600519", "name": "贵州茅台", "shares": 100, "avg_cost": 1750.25 }, { "symbol": "000001", "name": "平安银行", "shares": 500, "avg_cost": 12.88 } ] }但测试发现,当用户没有任何持仓时,后端返回data: [],而前端期望data: null。这时,你不能只改 JSON 内容,还要确保状态码是200,Content-Type正确,否则 Axios 会因 MIME 类型不匹配拒绝解析。
正确做法是创建mock/position-empty.json:
{ "code": 0, "msg": "success", "data": null }然后在 Charles 中配置 Map Local:
- Location:
https://prod-api.example.com/api/v1/position - Local Path:
./mock/position-empty.json - ✅Enable Map Local
- ✅Also map HTTPS requests(必须勾选,否则 HTTPS 请求不生效)
提示:Map Local 规则匹配是精确字符串匹配,不支持通配符。如果接口带 query 参数(如
/api/v1/position?userId=123),你需要把完整 URL 写进去,或者用更灵活的Rewrite功能(下文详述)。
3.2 断点拦截:Breakpoint 不是“暂停”,而是“流量闸门”
Breakpoint 拦截常被误解为“让请求卡住”,其实它是个双向阀门:你可以在请求发出前(Request)修改请求参数、Header、Body;也可以在响应返回前(Response)修改状态码、Header、Body。这才是“断点拦截”的完整能力。
以股票行情接口为例:后端提供/api/v1/stock/tick,但要求所有请求必须带X-Auth-TokenHeader,且 Token 必须是 JWT 格式。开发阶段,后端还没提供 Token 生成服务,你只能硬编码一个假 Token。但如果直接写死在代码里,上线时容易忘记删,造成安全风险。
解决方案:用 Breakpoint 在请求发出前注入 Header。
步骤:
- 在 Charles 中打开Proxy → Breakpoint Settings
- 点击Add,填入:
- Location:
https://prod-api.example.com/api/v1/stock/tick - Type:
Request - ✅Enable Breakpoint
- Location:
- 访问页面,触发该接口请求,Charles 会弹出 Breakpoint 窗口
- 在Request Headers标签页,点击Add Header,输入:
- Key:
X-Auth-Token - Value:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c(示例 JWT)
- Key:
- 点击Execute,请求继续发送
这样,所有对该 URL 的请求都会自动带上 Token,且只在 Charles 开启时生效,代码里完全干净。更进一步,你可以用Rewrite功能实现动态 Token 注入——比如从 localStorage 读取authToken,但需配合 Charles 的 JavaScript 扩展(稍后详解)。
注意:Breakpoint 是逐次生效的,每次请求都要手动点 Execute。如果想自动化,必须用 Rewrite 或 Map Remote(将请求转发到另一个 Mock 服务)。但手动模式恰恰是优势——它强迫你“看见”每一次请求,培养对数据流的敏感度。
3.3 规则改写进阶:Rewrite 功能解决动态参数难题
Map Local 解决静态数据,但真实接口充满动态参数:/api/v1/stock/tick?symbol=600519&market=SH、/api/v1/order/create?order_id=ORD-20231001-001。为每个参数组合建一个 JSON 文件不现实。
Charles 的Rewrite功能就是为此而生。它不是替换整个响应,而是像正则引擎一样,在响应体中查找、替换特定字符串或 JSON 路径。
例如,后端返回的股票价格是字符串"1823.50",但前端需要数字类型。你可以在 Rewrite 中添加规则:
- Location:
https://prod-api.example.com/api/v1/stock/tick - Type:
Response Body - Match Type:
JSON Path - JSON Path:
$.data.price - Replace With:
{{value}}→ 改为Number({{value}})
但更实用的是动态响应生成。Charles 支持 JavaScript 脚本,你可以在 Rewrite 中写 JS 逻辑:
// Rewrite Script for /api/v1/stock/tick function transform(response) { const body = JSON.parse(response.body.toString()); // 根据 query 参数动态修改 price const symbol = request.url.searchParams.get('symbol'); if (symbol === '600519') { body.data.price = 1823.5 + Math.random() * 10; // 模拟实时波动 } response.body = JSON.stringify(body); return response; }这个脚本在每次响应返回前执行,request对象包含完整请求信息(URL、Headers、Body),response对象可修改响应内容。它比 Map Local 灵活得多,且无需重启 Charles。
实操心得:Rewrite 脚本调试困难,Charles 不提供 console.log。我的技巧是:先在浏览器控制台写好逻辑,再复制进 Charles;或者用
alert(JSON.stringify(request))临时弹窗查看请求结构(仅限开发机,勿提交到团队)。
4. 实操过程:从零搭建金融行情联调环境
4.1 环境准备:Charles 安装与 HTTPS 解密配置
Charles 官网下载 macOS/Windows 版本(Linux 用户可用charles-proxy包),安装后首次启动会提示安装 SSL 证书。这一步必须做,且必须信任证书,否则所有 HTTPS 请求会失败。
具体步骤(以 macOS 为例):
- Charles →Help → SSL Proxying → Install Charles Root Certificate
- 系统弹出钥匙串访问窗口,找到
Charles Proxy CA证书,双击打开 - 展开信任,将SSL设为始终信任
- 关闭窗口,输入密码确认
- 回到 Charles,打开Proxy → SSL Proxying Settings
- 点击Add,填入:
- Host:
prod-api.example.com(你的目标域名,支持*通配符) - Port:
443 - ✅Enable SSL Proxying
- Host:
提示:如果目标域名是 IP(如
https://192.168.1.100:8443),Charles 无法解密,因为 SSL 证书绑定的是域名而非 IP。此时需让后端提供域名,或改用 HTTP 协议联调(仅限内网)。
4.2 规则改写实战:用 Map Local 模拟三种持仓状态
我们为用户持仓接口/api/v1/position创建三个 Mock 场景:
position-normal.json:正常持仓(2支股票)position-empty.json:空持仓(data: null)position-error.json:服务端错误(code: 500)
文件内容示例(position-error.json):
{ "code": 500, "msg": "Internal server error", "data": null }Charles 配置:
- Tools → Map Local
- 点击Add,填入:
- Local Path:
./mock/position-normal.json - Remote Host:
prod-api.example.com - Remote Path:
/api/v1/position - ✅Enable Map Local
- ✅Also map HTTPS requests
- Local Path:
- 复制两行,分别指向
position-empty.json和position-error.json,但Remote Path 保持相同—— Charles 会按顺序匹配,第一个启用的规则生效。
注意:Map Local 规则有优先级,越靠上的规则越先匹配。把最常用的
normal放在最上面,调试时只需开关它即可切换状态。
4.3 断点拦截实战:动态注入 Token 与模拟网络延迟
金融接口普遍要求鉴权,我们用 Breakpoint 注入X-Auth-Token,并模拟弱网环境。
步骤:
- Proxy → Breakpoint Settings → Add
- Location:
https://prod-api.example.com/api/v1/* - Type:
Request - ✅Enable Breakpoint
- Location:
- 访问页面,触发任意接口,Charles 弹出 Breakpoint 窗口
- 在Request Headers中添加:
X-Auth-Token:mock-token-123456
- 切换到Response标签页,点击Add Response Delay
- Delay:
2000ms(模拟 2 秒延迟)
- Delay:
- 点击Execute,观察前端加载状态
这样,所有/api/v1/下的请求都带 Token 且有 2 秒延迟,完美复现弱网体验。如果只想对特定接口延迟,把 Location 改为精确 URL 即可。
4.4 联调协同:如何让后端同事“看到”你的 Mock 规则?
联调不是单打独斗。我习惯把 Charles 规则导出为.chls文件,共享给后端:
- File → Export Session...,选择Charles Session Archive (.chls)
- 文件包含所有历史请求、响应、Breakpoint 设置、Map Local 规则
- 后端用 Charles 打开该文件,就能看到你模拟了哪些数据、修改了哪些 Header
更进一步,我用Charles 的 Export → Export as HAR功能,把关键请求导出为 HAR 文件(HTTP Archive),用在线工具(如 https://www.softwareishard.com/har/viewer/)可视化分析请求链路、耗时、Header,直接发给后端:“你看,这个请求我收到了,但响应里data是空数组,你们确认下契约”。
实操心得:不要只发 JSON 文件给后端,要发完整的请求上下文。有一次后端坚持说“接口没问题”,我导出 HAR 发过去,他一看请求里
X-Trace-ID是mock-123,立刻意识到是 Mock 环境问题,而不是代码 Bug。
5. 常见问题与排查技巧实录
5.1 HTTPS 请求不走 Charles?90% 是证书没信任
现象:Chrome 访问https://prod-api.example.com,Charles 日志里没有记录,Network 面板显示Pending。
原因:macOS/Windows 系统未信任 Charles 根证书,浏览器拒绝建立 HTTPS 连接。
排查步骤:
- 打开 Charles,确认Proxy → SSL Proxying Settings中目标域名已启用
- 在浏览器地址栏输入
chls.pro/ssl,下载并安装证书(Charles 会自动跳转) - 检查系统证书管理器:
- macOS:钥匙串访问 → 登录 → 证书 → 查找
Charles Proxy CA - Windows:
certmgr.msc→ 受信任的根证书颁发机构
- macOS:钥匙串访问 → 登录 → 证书 → 查找
- 双击证书 →信任→SSL设为始终信任
- 重启浏览器和 Charles
提示:iOS 设备需在 Safari 中访问
chls.pro/ssl,然后在「设置→通用→关于本机→证书信任设置」中开启 Charles 证书。
5.2 Map Local 生效但返回 404?路径大小写或斜杠惹的祸
现象:配置了Remote Path: /api/v1/position,但 Charles 日志显示404 Not Found。
原因:Charles 的 Map Local 匹配是严格字符串匹配,包括大小写和末尾斜杠。
排查方法:
- 在 Charles 日志中找到该请求,右键 →Copy → Copy URL,粘贴到文本编辑器
- 对比你配置的
Remote Path,检查:- 是否多了一个
/(如/api/v1/position/vs/api/v1/position) - 是否大小写不一致(如
/API/v1/position) - 是否有隐藏字符(Windows 换行符
\r\n)
- 是否多了一个
解决方案:用Rewrite替代 Map Local,或在Remote Path中使用通配符*(如/api/v1/position*)。
5.3 Breakpoint 不触发?Location 配置太宽泛
现象:设置了Location: https://prod-api.example.com/*,但只有部分请求触发 Breakpoint。
原因:*通配符只匹配路径,不匹配 query 参数。如果请求是https://prod-api.example.com/api/v1/position?cache_bust=123,Charles 会忽略?后的内容,但有时匹配不稳定。
解决方案:
- 用精确 URL:
https://prod-api.example.com/api/v1/position - 或用正则:
https://prod-api\.example\.com/api/v1/position.* - 在Breakpoint Settings中勾选Match against full URL(Charles 4.6+ 版本)
5.4 Mock 数据不更新?缓存机制在作祟
现象:修改了position-empty.json,但前端还是返回旧数据。
原因:浏览器或 Charles 缓存了响应。HTTP 缓存头(如Cache-Control: max-age=3600)会让浏览器复用旧响应。
排查与解决:
- 在 Charles 中,选中该请求 → 右键 →Clear Cache
- 在浏览器开发者工具 Network 面板,勾选Disable cache
- 在 Charles 的Proxy → Recording Settings中,取消勾选Use browser cache
- 给响应头加
Cache-Control: no-cache(通过 Rewrite 脚本)
5.5 金融类项目特殊问题:WebSocket 连接被拦截
现象:股票行情用 WebSocket(wss://prod-api.example.com/ws/tick),Charles 里看不到连接。
原因:Charles 默认不代理 WebSocket 流量,需手动开启。
解决步骤:
- Proxy → Recording Settings → Enable WebSocket recording
- 确保
wss://域名已在SSL Proxying Settings中启用 - 如果仍不行,尝试用
ws://(非加密)协议联调,或改用专用 WebSocket Mock 工具(如websocketd)
实操心得:金融项目对实时性要求高,WebSocket Mock 很难做到毫秒级精度。我的建议是:用 Charles Mock REST 接口验证业务逻辑,用真实 WebSocket 服务验证性能,两者分离。
6. 进阶技巧:用 Charles + Node.js 构建动态 Mock 服务
当 Mock 规则过于复杂(如需数据库查询、调用第三方 API),纯 Charles 难以胜任。这时,我用 Node.js 搭建轻量 Mock 服务,再用 Charles 的Map Remote将请求转发过去。
例如,模拟一个“根据用户 ID 返回定制化行情”的接口:
// mock-server.js const express = require('express'); const app = express(); app.get('/api/v1/stock/custom', (req, res) => { const userId = req.query.userId; // 从内存数据库查用户偏好 const preferences = { '1001': ['600519', '000001'], '1002': ['300750', '601318'] }; const symbols = preferences[userId] || ['600519']; // 调用真实行情 API(此处用 mock 数据) const data = symbols.map(symbol => ({ symbol, price: 1000 + Math.random() * 1000, change: (Math.random() - 0.5) * 2 })); res.json({ code: 0, data }); }); app.listen(3001, () => console.log('Mock server running on http://localhost:3001'));Charles 配置:
- Tools → Map Remote
- Remote Host:
prod-api.example.com - Remote Path:
/api/v1/stock/custom - Local Host:
localhost - Local Port:
3001 - ✅Enable Map Remote
这样,所有对/api/v1/stock/custom的请求,都被 Charles 转发到本地 Node.js 服务,实现动态逻辑。Node.js 服务可连接 MySQL、Redis,甚至调用 Wind 金融数据接口(需申请 API Key),真正打通数据闭环。
最后分享一个小技巧:我在团队里推行“Mock 规则即文档”。每个 Map Local/Rewrite 规则都配上注释,说明模拟场景、触发条件、预期结果,并存入 Git。新人入职第一天,拉取代码后运行
npm run mock:start,再打开 Charles 加载规则集,5 分钟就能跑通全流程——这才是 Mock 的终极价值:让联调从“人肉协调”变成“机器可执行”。