SpringBoot結(jié)合Vue實(shí)現(xiàn)Python在線調(diào)試器
項(xiàng)目概述
Python 在線調(diào)試器是一個(gè)基于 Web 的 Python 代碼執(zhí)行和調(diào)試工具,支持在線編寫、運(yùn)行和交互式調(diào)試 Python 代碼。項(xiàng)目采用前后端分離架構(gòu),前端負(fù)責(zé)用戶界面和交互,后端負(fù)責(zé)代碼執(zhí)行和調(diào)試邏輯。
技術(shù)棧
后端技術(shù)棧
| 技術(shù)/框架 | 版本 | 用途 |
|---|---|---|
| Java | 17 | 編程語言 |
| Spring Boot | 3.1.5 | Web框架 |
| Spring Web | - | RESTful API支持 |
| Spring Validation | - | 參數(shù)驗(yàn)證 |
| Jackson | - | JSON序列化/反序列化 |
| Maven | 3.6+ | 項(xiàng)目構(gòu)建和依賴管理 |
| Python | 3.x | 代碼執(zhí)行環(huán)境 |
核心依賴:
spring-boot-starter-web: Web開發(fā)支持spring-boot-starter-websocket: WebSocket支持(預(yù)留擴(kuò)展)spring-boot-starter-validation: 參數(shù)驗(yàn)證jackson-databind: JSON處理
前端技術(shù)棧
| 技術(shù)/框架 | 版本 | 用途 |
|---|---|---|
| Vue.js | 3.3.4 | 前端框架 |
| Vite | 5.0.0 | 構(gòu)建工具和開發(fā)服務(wù)器 |
| CodeMirror 6 | 6.x | 代碼編輯器 |
| Axios | 1.6.0 | HTTP客戶端 |
| Node.js | 16+ | 運(yùn)行環(huán)境 |
| npm | - | 包管理器 |
核心依賴:
@codemirror/lang-python: Python語言支持@codemirror/view: 編輯器視圖@codemirror/state: 編輯器狀態(tài)管理@codemirror/theme-one-dark: 深色主題@vitejs/plugin-vue: Vite Vue插件
架構(gòu)設(shè)計(jì)
整體架構(gòu)
┌─────────────────────────────────────────────────────────┐
│ 瀏覽器 (Browser) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Vue 3 前端應(yīng)用 │ │
│ │ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ CodeMirror 6 │ │ Axios HTTP │ │ │
│ │ │ 編輯器 │ │ 客戶端 │ │ │
│ │ └──────────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────┬───────────────────────────────────────┘
│ HTTP/REST API
┌─────────────────┴───────────────────────────────────────┐
│ Spring Boot 后端 (Port: 8080) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PythonController │ │
│ │ (REST API 端點(diǎn)) │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ PythonExecutionService │ │
│ │ (代碼執(zhí)行和調(diào)試邏輯) │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ ProcessBuilder + Python Process │ │
│ │ (執(zhí)行Python代碼) │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ Python 3.x (系統(tǒng)安裝) │ │
│ │ - pdb (Python調(diào)試器) │ │
│ │ - 代碼執(zhí)行 │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
分層架構(gòu)
后端分層
Controller層 (PythonController)
↓
Service層 (PythonExecutionService)
↓
Process層 (Java ProcessBuilder)
↓
Python運(yùn)行時(shí)環(huán)境
前端分層
視圖層 (App.vue Template)
↓
邏輯層 (App.vue Script - Composition API)
↓
編輯器層 (CodeMirror 6)
↓
HTTP層 (Axios)
核心實(shí)現(xiàn)方法
1. 代碼執(zhí)行實(shí)現(xiàn)
1.1 后端實(shí)現(xiàn) (PythonExecutionService.executeCode)
核心步驟:
創(chuàng)建臨時(shí)文件
Path pythonFile = Paths.get(tempDir, "python_" + sessionId + ".py");
Files.write(pythonFile, code.getBytes("UTF-8"));
啟動(dòng)Python進(jìn)程
ProcessBuilder processBuilder = new ProcessBuilder(pythonCmd, pythonFile.toString());
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
Process process = processBuilder.start();
讀取輸出
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), "UTF-8"));
// 設(shè)置30秒超時(shí)
boolean finished = process.waitFor(30, TimeUnit.SECONDS);
清理資源
Files.deleteIfExists(pythonFile); runningProcesses.remove(sessionId);
關(guān)鍵技術(shù)點(diǎn):
- 使用
ProcessBuilder創(chuàng)建獨(dú)立的Python進(jìn)程 - 設(shè)置
PYTHONIOENCODING=utf-8確保中文輸出正確 - 使用臨時(shí)文件存儲用戶代碼
- 設(shè)置執(zhí)行超時(shí)防止死循環(huán)
- UTF-8編碼處理確保字符正確傳輸
2. 調(diào)試功能實(shí)現(xiàn)
2.1 斷點(diǎn)插入機(jī)制
實(shí)現(xiàn)方法:
行號映射表構(gòu)建
Map<Integer, Integer> lineMapping = new HashMap<>(); // 實(shí)際行號 -> 原始行號
斷點(diǎn)代碼注入
// 在斷點(diǎn)行之前插入 pdb.set_trace()
result.append(indentStr).append("pdb.set_trace() # Breakpoint at line ")
.append(originalLineNumber).append("\n");
行號映射記錄
- 為所有插入的代碼行建立映射
- 包括
import pdb、空行、pdb.set_trace()等 - 確保能準(zhǔn)確還原原始行號
2.2 交互式調(diào)試會話管理
DebugSession 類:
private static class DebugSession {
Process process; // Python進(jìn)程
BufferedWriter stdin; // 標(biāo)準(zhǔn)輸入流(發(fā)送pdb命令)
Path pythonFile; // 臨時(shí)Python文件
boolean isActive; // 會話是否激活
int currentLine; // 當(dāng)前執(zhí)行行號
StringBuilder outputBuffer; // 輸出緩沖區(qū)
StringBuilder errorBuffer; // 錯(cuò)誤緩沖區(qū)
Map<Integer, Integer> lineMapping; // 行號映射表
}
會話管理:
- 使用
ConcurrentHashMap存儲多個(gè)調(diào)試會話 - 支持并發(fā)調(diào)試多個(gè)用戶
- 自動(dòng)清理會話資源
2.3 PDB命令映射
支持的調(diào)試操作:
| 操作 | PDB命令 | 說明 |
|---|---|---|
| 繼續(xù)執(zhí)行 | c\n | continue - 繼續(xù)到下一個(gè)斷點(diǎn) |
| 單步執(zhí)行 | n\n | next - 執(zhí)行下一行(不進(jìn)入函數(shù)) |
| 步入 | s\n | step - 進(jìn)入函數(shù)內(nèi)部 |
| 步出 | u\n | up - 返回到調(diào)用者 |
實(shí)現(xiàn)方式:
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();
2.4 行號解析和映射
PDB輸出格式解析:
// PDB輸出格式: > /path/to/file.py(行號)function_name()
Pattern pattern = Pattern.compile(">\\s+[^\\(]*\\(\\s*(\\d+)\\s*\\)[^\n]*");
行號轉(zhuǎn)換:
- 從PDB輸出中提取實(shí)際行號
- 通過映射表轉(zhuǎn)換為原始行號
- 如果沒有精確匹配,向上查找最接近的行號
- 返回給前端顯示
3. 前端編輯器實(shí)現(xiàn)
3.1 CodeMirror 6 集成
編輯器初始化:
editorView.value = new EditorView({
doc: codeContent,
extensions: [
basicSetup, // 基礎(chǔ)功能
python(), // Python語言支持
oneDark, // 深色主題
breakpointGutter, // 斷點(diǎn)gutter
currentLineHighlight // 當(dāng)前行高亮
],
parent: editorContainer.value
})
3.2 斷點(diǎn)可視化
實(shí)現(xiàn)原理:
- 使用
GutterMarker創(chuàng)建斷點(diǎn)標(biāo)記 - 使用
StateField管理斷點(diǎn)狀態(tài) - 使用
RangeSet存儲斷點(diǎn)位置 - 支持點(diǎn)擊gutter區(qū)域切換斷點(diǎn)
關(guān)鍵代碼:
// 斷點(diǎn)標(biāo)記類
class BreakpointMarker extends GutterMarker {
toDOM() {
const span = document.createElement('span')
span.className = 'breakpoint-marker'
span.textContent = '●'
return span
}
}
// 斷點(diǎn)狀態(tài)字段
const breakpointState = StateField.define({
create() { return RangeSet.empty },
update(breakpoints, tr) {
// 處理斷點(diǎn)變更
}
})
3.3 當(dāng)前行高亮
實(shí)現(xiàn)方法:
// 當(dāng)前行裝飾器
const currentLineDecoration = Decoration.line({
class: 'cm-current-line'
})
// 當(dāng)前行狀態(tài)字段
const currentLineState = StateField.define({
create() { return RangeSet.empty },
update(currentLine, tr) {
// 更新當(dāng)前行位置
},
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);
}
關(guān)鍵技術(shù)點(diǎn)
1. 進(jìn)程管理
進(jìn)程啟動(dòng):
- 使用
ProcessBuilder創(chuàng)建獨(dú)立進(jìn)程 - 分離標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出
- 設(shè)置環(huán)境變量確保編碼正確
進(jìn)程控制:
- 使用
Process.waitFor(timeout)實(shí)現(xiàn)超時(shí)控制 - 使用
Process.destroyForcibly()強(qiáng)制終止 - 使用
ConcurrentHashMap管理多個(gè)進(jìn)程
2. 異步I/O處理
輸出讀?。?/strong>
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();
關(guān)鍵點(diǎn):
- 使用獨(dú)立線程讀取進(jìn)程輸出
- 使用同步塊保證線程安全
- 實(shí)時(shí)解析行號并更新狀態(tài)
3. 行號映射算法
問題:
- 插入
import pdb和pdb.set_trace()后行號會偏移 - PDB顯示的是插入后的行號,需要轉(zhuǎn)換為原始行號
解決方案:
- 構(gòu)建完整的行號映射表
- 精確匹配優(yōu)先
- 向上查找最接近的行號(最多10行)
- 如果找不到,使用估算方法
4. 編碼處理
UTF-8編碼設(shè)置:
// 后端
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
Files.write(pythonFile, code.getBytes("UTF-8"));
new InputStreamReader(process.getInputStream(), "UTF-8")
// 前端
// Axios自動(dòng)處理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
5. 會話管理
會話存儲:
ConcurrentHashMap<String, DebugSession> debugSessions ConcurrentHashMap<String, Process> runningProcesses
會話生命周期:
- 開始調(diào)試時(shí)創(chuàng)建會話
- 執(zhí)行調(diào)試命令時(shí)更新會話
- 調(diào)試完成或停止時(shí)清理會話
- 自動(dòng)清理臨時(shí)文件
API接口設(shè)計(jì)
1. 代碼執(zhí)行接口
接口: POST /api/python/execute
請求體:
{
"code": "print('Hello, World!')",
"sessionId": "session_123"
}
響應(yīng):
{
"output": "Hello, World!\n",
"error": "",
"success": true,
"sessionId": "session_123"
}
2. 調(diào)試接口
接口: 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"
}
響應(yīng):
{
"output": "> file.py(2)func()\n-> x = 10",
"error": "",
"success": true,
"currentLine": 2,
"sessionId": "session_123"
}
3. 停止執(zhí)行接口
接口: POST /api/python/stop/{sessionId}
響應(yīng):
執(zhí)行已停止
調(diào)試功能實(shí)現(xiàn)原理
1. 斷點(diǎn)插入流程
原始代碼 插入后代碼
───────────────── ─────────────────
1 def func(): 1 import pdb
2 x = 10 2
3 return x 3 def func():
4 pdb.set_trace() # Breakpoint at line 2
5 x = 10
6 return x
行號映射:
實(shí)際行號 -> 原始行號
4 -> 2
5 -> 2
2. PDB交互流程
前端 后端 Python進(jìn)程
│ │ │
│-- startDebug ---->│ │
│ │-- 創(chuàng)建臨時(shí)文件 ----->│
│ │-- 啟動(dòng)進(jìn)程 --------->│
│ │<-- PDB暫停在第N行 ---│
│<-- 返回行號N -----│ │
│ │ │
│-- step ---------->│ │
│ │-- 發(fā)送 's\n' ------->│
│ │ │-- 步入函數(shù)
│ │<-- PDB暫停在第M行 ---│
│<-- 返回行號M -----│ │
3. 行號解析流程
PDB輸出: "> file.py(15)func()\n-> x = 10"
↓
正則匹配: Pattern.compile(">\s+[^\(]*\(\s*(\d+)\s*\)")
↓
提取行號: 15
↓
查找映射: lineMapping.get(15) = 12
↓
返回前端: currentLine = 12
前端交互實(shí)現(xiàn)
1. Vue 3 Composition API
響應(yīng)式狀態(tài):
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)
})
2. 斷點(diǎn)管理
添加斷點(diǎn):
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()
}
}
}
斷點(diǎn)同步:
watch(breakpoints, () => {
nextTick(() => {
syncBreakpointsToEditor()
})
}, { deep: true })
3. 調(diào)試控制
調(diào)試命令執(zhí)行:
const executeDebugCommand = async (action) => {
const response = await axios.post(`${API_BASE}/debug`, {
code: '',
sessionId: sessionId.value,
breakpoints: [],
action: action // 'continue', 'step', 'stepOver', 'stepOut'
})
// 更新當(dāng)前行號并高亮
if (result.currentLine) {
currentDebugLine.value = result.currentLine
highlightCurrentLine(result.currentLine)
}
}
鍵盤快捷鍵:
- F5: 繼續(xù)執(zhí)行
- F7: 步入
- F8: 單步執(zhí)行
- Shift+F8: 步出
4. 實(shí)時(shí)更新機(jī)制
當(dāng)前行高亮更新:
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)
]
})
}
部署方案
附代碼倉庫鏈接:https://gitee.com/ghostmen/python-debug-demo
開發(fā)環(huán)境
后端:
- 端口:8080
- 啟動(dòng):
mvn spring-boot:run - 或使用:
start-backend.bat/start-backend.sh
前端:
- 端口:3000
- 啟動(dòng):
npm run dev - 或使用:
start-frontend.bat/start-frontend.sh - Vite代理:
/api→http://localhost:8080
生產(chǎn)環(huán)境建議
后端:
- 打包:
mvn clean package - 運(yùn)行:
java -jar target/python-debug-backend-1.0.0.jar - 配置:修改
application.properties - 反向代理:Nginx
前端:
- 構(gòu)建:
npm run build - 輸出目錄:
dist/ - 靜態(tài)資源服務(wù)器:Nginx / Apache
- 或集成到后端靜態(tài)資源
安全建議
代碼執(zhí)行限制
- 添加沙箱機(jī)制
- 限制系統(tǒng)調(diào)用
- 限制資源使用(CPU、內(nèi)存)
網(wǎng)絡(luò)安全
- 配置具體的CORS允許域名
- 使用HTTPS
- 添加身份驗(yàn)證
輸入驗(yàn)證
- 驗(yàn)證代碼長度
- 過濾危險(xiǎn)操作
- 設(shè)置執(zhí)行超時(shí)
性能優(yōu)化
1. 進(jìn)程管理優(yōu)化
- 限制并發(fā)執(zhí)行的進(jìn)程數(shù)
- 及時(shí)清理已完成的進(jìn)程
- 使用線程池管理I/O操作
2. 前端優(yōu)化
- 代碼編輯器懶加載
- 輸出內(nèi)容虛擬滾動(dòng)(大量輸出時(shí))
- 防抖處理頻繁的斷點(diǎn)操作
3. 緩存策略
- 緩存Python命令檢測結(jié)果
- 復(fù)用調(diào)試會話(如果可能)
擴(kuò)展方案
1. WebSocket實(shí)時(shí)交互
優(yōu)勢:
- 實(shí)時(shí)雙向通信
- 更好的調(diào)試體驗(yàn)
- 支持?jǐn)帱c(diǎn)處的變量查看
實(shí)現(xiàn)方向:
- 使用 Spring WebSocket
- 前端使用 WebSocket API
- 實(shí)時(shí)推送調(diào)試狀態(tài)
2. 使用debugpy替代pdb
優(yōu)勢:
- 更專業(yè)的調(diào)試協(xié)議(DAP)
- 更好的性能
- 支持更多調(diào)試功能
實(shí)現(xiàn)方向:
- 集成debugpy庫
- 實(shí)現(xiàn)DAP協(xié)議客戶端
- 支持變量查看、表達(dá)式求值等
3. 多文件支持
實(shí)現(xiàn)方向:
- 文件管理器組件
- 多標(biāo)簽編輯器
- 文件間依賴管理
4. 代碼補(bǔ)全
實(shí)現(xiàn)方向:
- 集成Python語言服務(wù)器(如Pyright)
- CodeMirror自動(dòng)補(bǔ)全擴(kuò)展
- 提供代碼提示和錯(cuò)誤檢查
技術(shù)難點(diǎn)與解決方案
難點(diǎn)1: 行號映射準(zhǔn)確性
問題: 插入調(diào)試代碼后,行號偏移,需要準(zhǔn)確映射回原始行號。
解決方案:
- 建立完整的行號映射表
- 使用向上查找算法作為備選
- 智能匹配最接近的行號
難點(diǎn)2: PDB輸出解析
問題: PDB輸出格式多樣,需要準(zhǔn)確提取當(dāng)前行號。
解決方案:
- 使用正則表達(dá)式匹配多種格式
- 從后往前查找最新的PDB提示符
- 容錯(cuò)處理,支持多種輸出格式
難點(diǎn)3: 異步I/O同步
問題: 異步讀取輸出與同步操作之間的時(shí)序問題。
解決方案:
- 使用同步塊保護(hù)共享資源
- 合理的等待時(shí)間
- 狀態(tài)標(biāo)志控制異步讀取
難點(diǎn)4: 編碼問題
問題: Windows系統(tǒng)默認(rèn)GBK編碼,導(dǎo)致中文亂碼。
解決方案:
- 設(shè)置
PYTHONIOENCODING=utf-8環(huán)境變量 - 統(tǒng)一使用UTF-8編碼
- Spring Boot配置UTF-8響應(yīng)編碼
總結(jié)
本項(xiàng)目采用前后端分離架構(gòu),使用Spring Boot 3.x和Vue 3構(gòu)建,通過ProcessBuilder執(zhí)行Python代碼,使用pdb實(shí)現(xiàn)交互式調(diào)試。核心特點(diǎn):
- 技術(shù)選型合理:現(xiàn)代化的技術(shù)棧,易于維護(hù)和擴(kuò)展
- 實(shí)現(xiàn)方案可行:使用成熟的ProcessBuilder和pdb,穩(wěn)定性好
- 用戶體驗(yàn)良好:可視化斷點(diǎn)、當(dāng)前行高亮、快捷鍵支持
- 擴(kuò)展性強(qiáng):預(yù)留WebSocket接口,可升級到更專業(yè)的調(diào)試方案
改進(jìn)方向:
- 使用debugpy實(shí)現(xiàn)更專業(yè)的調(diào)試
- 添加WebSocket實(shí)現(xiàn)實(shí)時(shí)交互
- 增強(qiáng)安全性和性能優(yōu)化
- 支持更多調(diào)試功能(變量查看、表達(dá)式求值等)
以上就是SpringBoot結(jié)合Vue實(shí)現(xiàn)Python在線調(diào)試器的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot在線調(diào)試Python的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用spring stream發(fā)送消息代碼實(shí)例
這篇文章主要介紹了使用spring stream發(fā)送消息代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
springboot多節(jié)點(diǎn)應(yīng)用里的雪花算法唯一性詳解
雪花算法在單節(jié)點(diǎn)下唯一,但在多副本Kubernetes環(huán)境中可能重復(fù),通過修改Pod名稱生成workId,解決了這個(gè)問題,同時(shí)避免了第三方組件和網(wǎng)絡(luò)請求,本文給大家介紹springboot多節(jié)點(diǎn)應(yīng)用里的雪花算法唯一性,感興趣的朋友一起看看吧2024-12-12
教你開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細(xì)過程
這篇文章主要介紹了開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細(xì)過程,集成包括引入依賴配置文件及訪問驗(yàn)證的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05
Java獲取Process子進(jìn)程進(jìn)程ID方法詳解
這篇文章主要介紹了Java獲取Process子進(jìn)程進(jìn)程ID方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12
使用AOP攔截Controller獲取@PathVariable注解傳入的參數(shù)
這篇文章主要介紹了使用AOP攔截Controller獲取@PathVariable注解傳入的參數(shù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08

