项目概述
Python 在线调试器是一个基于 Web 的 Python 代码执行和调试工具,支持在线编写、运行和交互式调试 Python 代码。项目采用前后端分离架构,前端负责用户界面和交互,后端负责代码执行和调试逻辑。
档介绍了基于 SpringBoot 和 Vue 实现的 Python 在线调试器技术方案。系统采用前后端分离架构,后端使用 Java 通过 ProcessBuilder 调用 Python 进程执行代码,利用 pdb 模块实现断点调试;前端使用 Vue 3 和 CodeMirror 6 提供代码编辑与交互界面。内容涵盖技术栈选型、架构设计、核心实现(代码执行、断点插入、PDB 命令映射)、API 接口设计、部署方案及安全建议。重点解决了行号映射、异步 I/O 处理及编码问题,并提供了性能优化与扩展方向。
Python 在线调试器是一个基于 Web 的 Python 代码执行和调试工具,支持在线编写、运行和交互式调试 Python 代码。项目采用前后端分离架构,前端负责用户界面和交互,后端负责代码执行和调试逻辑。
| 技术/框架 | 版本 | 用途 |
|---|
| Java | 17 | 编程语言 |
| Spring Boot | 3.1.5 | Web 框架 |
| Spring Web | - | RESTful API 支持 |
| Spring Validation | - | 参数验证 |
| Jackson | - | JSON 序列化/反序列化 |
| Maven | 3.6+ | 项目构建和依赖管理 |
| Python | 3.x | 代码执行环境 |
核心依赖:
spring-boot-starter-web: Web 开发支持spring-boot-starter-websocket: WebSocket 支持(预留扩展)spring-boot-starter-validation: 参数验证jackson-databind: JSON 处理| 技术/框架 | 版本 | 用途 |
|---|---|---|
| Vue.js | 3.3.4 | 前端框架 |
| Vite | 5.0.0 | 构建工具和开发服务器 |
| CodeMirror 6 | 6.x | 代码编辑器 |
| Axios | 1.6.0 | HTTP 客户端 |
| Node.js | 16+ | 运行环境 |
| npm | - | 包管理器 |
核心依赖:
@codemirror/lang-python: Python 语言支持@codemirror/view: 编辑器视图@codemirror/state: 编辑器状态管理@codemirror/theme-one-dark: 深色主题@vitejs/plugin-vue: Vite Vue 插件┌─────────────────────────────────────────────────────────┐
│ 浏览器 (Browser) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Vue 3 前端应用 │ │
│ │ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ CodeMirror 6 │ │ Axios HTTP │ │ │
│ │ │ 编辑器 │ │ 客户端 │ │ │
│ │ └──────────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────┬───────────────────────────────────────┘
│ HTTP/REST API
┌─────────────────┴───────────────────────────────────────┐
│ Spring Boot 后端 (Port: 8080) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PythonController │ │
│ │ (REST API 端点) │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ PythonExecutionService │ │
│ │ (代码执行和调试逻辑) │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ ProcessBuilder + Python Process │ │
│ │ (执行 Python 代码) │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ Python 3.x (系统安装) │ │
│ │ - pdb (Python 调试器) │ │
│ │ - 代码执行 │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
Controller 层 (PythonController)
↓
Service 层 (PythonExecutionService)
↓
Process 层 (Java ProcessBuilder)
↓
Python 运行时环境
视图层 (App.vue Template)
↓
逻辑层 (App.vue Script - Composition API)
↓
编辑器层 (CodeMirror 6)
↓
HTTP 层 (Axios)
核心步骤:
清理资源
Files.deleteIfExists(pythonFile);
runningProcesses.remove(sessionId);
读取输出
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), "UTF-8"));
// 设置 30 秒超时
boolean finished = process.waitFor(30, TimeUnit.SECONDS);
启动 Python 进程
ProcessBuilder processBuilder = new ProcessBuilder(pythonCmd, pythonFile.toString());
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
Process process = processBuilder.start();
创建临时文件
Path pythonFile = Paths.get(tempDir, "python_" + sessionId + ".py");
Files.write(pythonFile, code.getBytes("UTF-8"));
关键技术点:
ProcessBuilder 创建独立的 Python 进程PYTHONIOENCODING=utf-8 确保中文输出正确实现方法:
import pdb、空行、pdb.set_trace() 等断点代码注入
// 在断点行之前插入 pdb.set_trace()
result.append(indentStr).append("pdb.set_trace() # Breakpoint at line ")
.append(originalLineNumber).append("\n");
行号映射表构建
Map<Integer, Integer> lineMapping = new HashMap<>(); // 实际行号 -> 原始行号
DebugSession 类:
private static class DebugSession {
Process process; // Python 进程
BufferedWriter stdin; // 标准输入流(发送 pdb 命令)
Path pythonFile; // 临时 Python 文件
boolean isActive; // 会话是否激活
int currentLine; // 当前执行行号
StringBuilder outputBuffer; // 输出缓冲区
StringBuilder errorBuffer; // 错误缓冲区
Map<Integer, Integer> lineMapping; // 行号映射表
}
会话管理:
ConcurrentHashMap 存储多个调试会话支持的调试操作:
| 操作 | PDB 命令 | 说明 |
|---|---|---|
| 继续执行 | c\n | continue - 继续到下一个断点 |
| 单步执行 | n\n | next - 执行下一行(不进入函数) |
| 步入 | s\n | step - 进入函数内部 |
| 步出 | u\n | up - 返回到调用者 |
实现方式:
String pdbCommand;
switch (action) {
case "continue": pdbCommand = "c\n"; break;
case "step": pdbCommand = "s\n"; break;
case "stepOver": pdbCommand = "n\n"; break;
case "stepOut": pdbCommand = "u\n"; break;
}
session.stdin.write(pdbCommand);
session.stdin.flush();
PDB 输出格式解析:
// PDB 输出格式:> /path/to/file.py(行号)function_name()
Pattern pattern = Pattern.compile(">\\s+[^\\(]*\\(\\s*(\\d+)\\s*\\)[^\n]*");
行号转换:
编辑器初始化:
editorView.value = new EditorView({
doc: codeContent,
extensions: [
basicSetup, // 基础功能
python(), // Python 语言支持
oneDark, // 深色主题
breakpointGutter, // 断点 gutter
currentLineHighlight // 当前行高亮
],
parent: editorContainer.value
});
实现原理:
GutterMarker 创建断点标记StateField 管理断点状态RangeSet 存储断点位置关键代码:
// 断点标记类
class BreakpointMarker extends GutterMarker {
toDOM() {
const span = document.createElement('span');
span.className = 'breakpoint-marker';
span.textContent = '●';
return span;
}
}
// 断点状态字段
const breakpointState = StateField.define({
create() { return RangeSet.empty; },
update(breakpoints, tr) { /* 处理断点变更 */ }
});
实现方法:
// 当前行装饰器
const currentLineDecoration = Decoration.line({ class: 'cm-current-line' });
// 当前行状态字段
const currentLineState = StateField.define({
create() { return RangeSet.empty; },
update(currentLine, tr) { /* 更新当前行位置 */ },
provide: f => EditorView.decorations.from(f)
});
样式定义:
.cm-current-line {
background-color: rgba(78, 148, 255, 0.15);
outline: 1px solid rgba(78, 148, 255, 0.3);
}
进程启动:
ProcessBuilder 创建独立进程进程控制:
Process.waitFor(timeout) 实现超时控制Process.destroyForcibly() 强制终止ConcurrentHashMap 管理多个进程输出读取:
Thread outputThread = new Thread(() -> {
try (BufferedReader reader = ...) {
String line;
while ((line = reader.readLine()) != null && session.isActive) {
synchronized (session.outputBuffer) {
session.outputBuffer.append(line).append("\n");
}
}
}
});
outputThread.start();
关键点:
问题:
import pdb 和 pdb.set_trace() 后行号会偏移解决方案:
UTF-8 编码设置:
// 后端
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
Files.write(pythonFile, code.getBytes("UTF-8"));
new InputStreamReader(process.getInputStream(), "UTF-8")
// 前端
// Axios 自动处理 UTF-8 编码
配置文件:
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
会话存储:
ConcurrentHashMap<String, DebugSession> debugSessions
ConcurrentHashMap<String, Process> runningProcesses
会话生命周期:
接口: POST /api/python/execute
请求体:
{
"code": "print('Hello, World!')",
"sessionId": "session_123"
}
响应:
{
"output": "Hello, World!\n",
"error": "",
"success": true,
"sessionId": "session_123"
}
接口: POST /api/python/debug
请求体:
{
"code": "def func():\n x = 10\n return x",
"sessionId": "session_123",
"breakpoints": [2, 3],
"action": "start" | "continue" | "step" | "stepOver" | "stepOut"
}
响应:
{
"output": "> file.py(2)func()\n-> x = 10",
"error": "",
"success": true,
"currentLine": 2,
"sessionId": "session_123"
}
接口: POST /api/python/stop/{sessionId}
响应:
执行已停止
原始代码 插入后代码
───────────────── ─────────────────
1 def func(): 1 import pdb
2 x = 10 3 def func():
3 return x 4 pdb.set_trace() # Breakpoint at line 2
5 x = 10
6 return x
行号映射:实际行号 -> 原始行号
4 -> 2
5 -> 2
前端 后端 Python 进程
│ │ │
│-- startDebug ---->│ │
│-- 创建临时文件 ----->│ │
│-- 启动进程 --------->│ │
│<-- PDB 暂停在第 N 行 ---│ │
│<-- 返回行号N -----│ │
│ │ │
│ │ │
│-- step ---------->│ │
│-- 发送 's\n' ------->│ │
│-- 步入函数 │ │
│<-- PDB 暂停在第 M 行 ---│ │
│<-- 返回行号M -----│ │
PDB 输出:"> file.py(15)func()\n-> x = 10"
↓
正则匹配:Pattern.compile(">\s+[^\(]*\(\s*(\d+)\s*\)")
↓
提取行号:15
↓
查找映射:lineMapping.get(15) = 12
↓
返回前端:currentLine = 12
响应式状态:
const breakpoints = ref([]);
const currentDebugLine = ref(null);
const isInDebugMode = ref(false);
生命周期管理:
onMounted(() => {
initEditor();
sessionId.value = generateSessionId();
window.addEventListener('keydown', handleKeyPress);
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyPress);
})
添加断点:
const addBreakpoint = () => {
if (newBreakpoint.value && newBreakpoint.value > 0) {
if (!breakpoints.value.includes(lineNum)) {
breakpoints.value.push(lineNum);
breakpoints.value.sort((a, b) => a - b);
syncBreakpointsToEditor();
}
}
};
断点同步:
watch(breakpoints, () => {
nextTick(() => {
syncBreakpointsToEditor();
});
}, { deep: true });
调试命令执行:
const executeDebugCommand = async (action) => {
const response = await axios.post(`${API_BASE}/debug`, {
code: '',
sessionId: sessionId.value,
breakpoints: [],
action: action // 'continue', 'step', 'stepOver', 'stepOut'
});
// 更新当前行号并高亮
if (result.currentLine) {
currentDebugLine.value = result.currentLine;
highlightCurrentLine(result.currentLine);
}
};
键盘快捷键:
当前行高亮更新:
const highlightCurrentLine = (lineNum) => {
const view = editorView.value;
const line = view.state.doc.line(lineNum);
view.dispatch({
effects: [
EditorView.scrollIntoView(line.from, { y: 'center' }),
setCurrentLineEffect.of(line.from)
]
});
};
后端:
mvn spring-boot:runstart-backend.bat / start-backend.sh前端:
npm run devstart-frontend.bat / start-frontend.sh/api → http://localhost:8080后端:
mvn clean packagejava -jar target/python-debug-backend-1.0.0.jarapplication.properties前端:
npm run builddist/优势:
实现方向:
优势:
实现方向:
实现方向:
实现方向:
问题: 插入调试代码后,行号偏移,需要准确映射回原始行号。
解决方案:
问题: PDB 输出格式多样,需要准确提取当前行号。
解决方案:
问题: 异步读取输出与同步操作之间的时序问题。
解决方案:
问题: Windows 系统默认 GBK 编码,导致中文乱码。
解决方案:
PYTHONIOENCODING=utf-8 环境变量本项目采用前后端分离架构,使用 Spring Boot 3.x 和 Vue 3 构建,通过 ProcessBuilder 执行 Python 代码,使用 pdb 实现交互式调试。核心特点:
改进方向:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online