断点调试
断点是你在代码某一行做的标记。程序运行到这一行时会暂停,这时你可以看到每个变量当前的值,再一步一步往下执行,看程序到底走了哪条路。这种找问题的方法叫断点调试。
它和排查问题里讲的方法不一样:
| 方法 | 适合什么 |
|---|---|
| 看日志、按 traceId 搜索 | 事后回看:请求已经结束了,看它留下的记录 |
| 断点调试 | 现场观察:让程序停在某一行,直接看变量的值 |
两种方法经常一起用:先用 traceId 和日志确定问题大概在哪里,再到那里打断点细看。
前端(电脑端)
准备
在仓库根目录运行 pnpm dev(macOS 和 Windows 命令相同),然后按终端打印的前端地址在浏览器里打开,默认是 http://localhost:5173。
开发模式下,前端由 Vite(前端的开发服务器和构建工具)提供。浏览器实际运行的是编译后的代码,但 Vite 会同时提供 source map(源码映射:记录编译后的代码对应源码的哪一行),所以在开发者工具里看到和打断点的,都是你写的 .vue 和 .ts 原文件。
打开开发者工具
推荐用 Edge 或 Chrome,两者的开发者工具是一样的。
| 系统 | 快捷键 |
|---|---|
| Windows | F12 或 Ctrl + Shift + I |
| macOS | Cmd + Option + I |
也可以在页面上点右键,选检查。
在 Sources 面板里打断点
- 切换到 Sources(源代码)面板;
- 按
Ctrl + P(macOS 是Cmd + P),输入文件名,比如position,从列表里选中要看的文件; - 点击代码左边的行号,行号上出现一个蓝色标记,断点就打好了。再点一次是取消;
- 在页面上做一次会执行到这一行的操作,比如点搜索按钮。程序停下来,这一行被高亮。
程序暂停后,右侧可以看到:
- Scope(作用域):当前能访问到的所有变量和它们的值;
- Watch(监视):点
+输入一个变量名或表达式,比如form.name,它的值会一直显示在这里; - Call Stack(调用栈):程序是经过哪些函数一层层调用到这里的。
把鼠标停在代码里的变量上,也能直接看到它的值。
用面板上方的按钮(或快捷键)控制执行:
| 按钮 | 快捷键 | 作用 |
|---|---|---|
| 继续 | F8 | 一直运行到下一个断点 |
| 单步跳过 | F10 | 执行当前这一行,停在下一行;遇到函数调用不进去 |
| 单步进入 | F11 | 遇到函数调用时,进到函数里面 |
| 单步跳出 | Shift + F11 | 执行完当前函数,回到调用它的地方 |
用代码打断点:debugger
在代码里写一行 debugger,开发者工具打开时,程序运行到这里就会暂停,效果和点行号一样:
function onSubmit() {
debugger // 运行到这里暂停
// …
}找不到文件、或者代码在很深的地方时,这样更方便。
用完一定要删掉
debugger 只用于本地调试,不能提交。电脑端的代码检查会把它当成错误:pnpm lint(pnpm verify 的第一项)会报 no-debugger,检查不通过。
在 Network 面板里看请求
Network(网络)面板列出页面发出的所有请求。点一个请求,可以看到:
- Headers(请求头和响应头):请求地址、方法、状态码,以及响应头里的
X-Request-Id; - Payload(请求参数):前端发出去的数据;
- Response(响应内容):后端返回的数据。
本项目的接口都用统一的格式返回:
{ "code": 0, "msg": "ok", "data": { "…": "…" } }code 是 0 表示成功,数据在 data 里。出错时返回的是错误码、翻译好的错误信息和请求编号:
{ "code": "…", "msg": "…", "traceId": "6f1c…" }traceId(请求编号)就是每个请求的编号,和响应头 X-Request-Id 的值相同。前端的问题查到最后发现是后端返回了错误时,把 traceId 复制下来,到服务端日志里搜索,方法见排查问题。
先分清是前端还是后端的问题
在 Network 面板里看一眼响应:后端返回的数据是对的、页面显示错了,是前端的问题,在前端打断点;后端返回的就是错的,再去查后端。
Console 面板
Console(控制台)面板显示前端的报错和 console.log 的输出。页面不正常时先看这里有没有红色的报错,点报错右边的文件名可以跳到出错的那一行。
程序停在断点上时,也可以在 Console 里直接输入变量名或表达式,看它的值。
Vue DevTools
Vue DevTools 是 Vue 官方的浏览器扩展,在 Chrome 应用商店或 Edge 加载项商店里搜索 Vue.js devtools 安装。安装后,开发者工具里会多出一个 Vue 面板,可以看到:
- 组件树:页面由哪些组件组成,每个组件的 props 和状态;
- Pinia:本项目用 Pinia 管理全局状态(比如
auth存当前登录用户和权限),可以直接看到每个 store 里的数据; - 路由:当前路由和所有已注册的路由。
Vue DevTools 只在 pnpm dev 的开发模式下可用,构建后的正式版本看不到。
也可以在 VS Code 里调试前端:仓库自带两个调试配置 Web: Edge (Vite dev server) 和 Web: Chrome (Vite dev server)(在 .vscode/launch.json 里)。pnpm dev 运行时,在 VS Code 的运行和调试里选其中一个,它会打开一个可调试的浏览器并访问 http://localhost:5173,断点直接打在 VS Code 里 apps/web/src 的文件上。这不是必需的,用浏览器自带的开发者工具效果一样。
手机端
移动端有三种运行方式,调试工具各不相同。怎么启动它们,见移动端开发。
| 平台 | 在哪里调试 |
|---|---|
| H5 | 浏览器开发者工具,和上面电脑端的方法完全一样。点开发者工具左上角的设备工具栏图标(Ctrl + Shift + M,macOS 是 Cmd + Shift + M),把页面切换成手机尺寸 |
| 微信小程序 | 运行 pnpm -C mobile dev:mp-weixin,在微信开发者工具里导入 mobile/dist/dev/mp-weixin。它自带调试器,也有 Console、Sources、Network 面板,用法和浏览器差不多 |
| App | 运行 pnpm -C mobile dev:app,在 HBuilderX 里导入 mobile/dist/dev/app,运行到手机或模拟器。App 打印的日志和报错显示在 HBuilderX 下方的控制台里 |
日常写页面优先用 H5 调试,最方便;小程序和 App 用来核对只在那个平台上才有的问题。
后端
后端(NestJS 服务端)也能像前端一样打断点:程序停在某一行,在编辑器里看变量、单步执行。做法是让后端进程打开 Node.js 的调试端口(inspector:调试器通过这个端口连上正在运行的 Node 进程),再用 VS Code 或 WebStorm 附加(attach)上去。“附加”的意思是:程序已经在运行,调试器连上去,而不是由调试器重新启动程序。
第一步:带调试端口启动开发服务
平时用 pnpm dev 启动开发服务。要调试后端时,改成在启动前设置环境变量 QW_INSPECT=1:
QW_INSPECT=1 pnpm dev$env:QW_INSPECT=1; pnpm.cmd devPowerShell 的设置会保留在当前终端里,之后再在这个终端运行 pnpm dev 也会带上调试端口。不需要时用 Remove-Item Env:QW_INSPECT 清除,或者关闭这个终端。
用 Windows 的 cmd(命令提示符)时,先运行 set "QW_INSPECT=1",再运行 call pnpm.cmd dev;用完运行 set "QW_INSPECT=" 清除。
这样启动后:
- 只有后端应用进程打开调试端口,地址是
127.0.0.1:9229。pnpm dev里同时运行的 TypeScript 编译(tsc)和前端的 Vite 不受影响; - 端口只监听本机(
127.0.0.1),局域网里的其他电脑连不上; - 同时打开了 source map(源码映射),所以断点可以直接打在 TypeScript 源文件上,不用去看编译后的 JavaScript。
QW_INSPECT 可以取这些值:
| 值 | 效果 |
|---|---|
1 或 true | 打开调试端口 9229 |
1024 到 65535 之间的端口号,比如 9230 | 换成这个端口。这时要把 .vscode/launch.json 里后端配置的 port 改成相同的值 |
0、false 或不设置 | 不打开调试端口,和平时的 pnpm dev 一样 |
| 其他值 | 终端里打印一条警告,然后忽略,不打开调试端口 |
9229 被别的程序占用时(比如另一个项目也在调试),就换一个端口号。
不要用全局的 NODE_OPTIONS=--inspect
网上常见的做法是设置 NODE_OPTIONS=--inspect。在本项目里不要这样做:pnpm dev 会启动好几个 Node 进程(tsc、后端、Vite……),每个进程都会去抢同一个调试端口。用 QW_INSPECT,只有后端应用进程打开调试端口。
第二步:在 VS Code 里附加
仓库里提交了 VS Code 的调试配置文件 .vscode/launch.json,不用自己写。
- 用 VS Code 打开仓库根目录;
- 确认第一步的
pnpm dev正在运行,终端里后端已经启动; - 点左侧栏的运行和调试(Run and Debug,快捷键
Ctrl + Shift + D,macOS 是Cmd + Shift + D); - 在顶部的下拉框里选 Server: attach (pnpm dev with QW_INSPECT=1),点旁边的绿色三角形开始。连上后,VS Code 底部的状态栏会变色;
- 打开
apps/server/src(后端代码)或packages/shared/src(前后端共用的代码)里的.ts文件,点行号左边打断点,出现红点就打好了; - 到浏览器的页面上做一次会调用这段代码的操作,比如在列表页点搜索,让前端发出请求;
- 程序停在断点上。左侧可以看到变量(Variables)、监视(Watch)和调用堆栈(Call Stack),把鼠标停在代码里的变量上也能看到它的值。
控制执行的按钮在编辑器顶部的调试工具栏上,快捷键和浏览器开发者工具差不多:
| 按钮 | 快捷键 | 作用 |
|---|---|---|
| 继续 | F5 | 一直运行到下一个断点 |
| 单步跳过 | F10 | 执行当前这一行,停在下一行;遇到函数调用不进去 |
| 单步进入 | F11 | 遇到函数调用时,进到函数里面 |
| 单步跳出 | Shift + F11 | 执行完当前函数,回到调用它的地方 |
| 断开 | Shift + F5 | 断开调试器,后端继续运行 |
程序停在断点上时,请求在等待
后端停在断点上的这段时间,这个请求一直没有返回,页面会一直转圈。停太久,前端可能显示请求超时,这是正常的,按继续让程序跑完就好。
改代码后自动重新附加
开发模式下,保存后端的代码文件后,后端会自动重新编译并重启。重启后调试端口不变,VS Code 的这个配置会自动重新附加,断点也还在,不用再点一次开始。
要注意的是启动阶段的代码,比如模块初始化(onModuleInit)和应用启动入口(bootstrap)。后端重启时,这些代码在调试器重新附加之前就已经执行完了,所以第一次附加时断不到。办法是:先附加上,再修改并保存一次后端的文件(比如加一个空行)触发重启,这时调试器已经在等待,就能停在启动阶段的断点上。
WebStorm
WebStorm(以及其他 JetBrains 编辑器)没有使用 .vscode/launch.json。新建一个 Attach to Node.js/Chrome 类型的运行配置,主机填 127.0.0.1,端口填 9229(或你在 QW_INSPECT 里写的端口),勾选重新连接(Reconnect automatically),然后以调试方式运行这个配置即可。断点同样打在 TypeScript 源文件上。
仓库里的说明见快速开始 · 断点调试。断点调试从 1.3.1 版开始提供;更早的版本请用 traceId 加日志、用 Swagger 直接调接口来排查,方法见排查问题。