本文将介绍如何在 Electron 应用中构建一个安全的代码运行环境,用于验证 AI 生成的 JavaScript 和 Python 代码。
背景与需求
在开发 AI 辅助编程工具时,我们经常需要验证 AI 生成的代码片段是否能正确运行。这要求我们构建一个安全的沙箱环境,让用户能够:
- 在隔离环境中运行 JavaScript/HTML 代码
- 执行 Python 代码并查看输出结果
- 确保主机系统安全,防止恶意代码破坏
技术架构
整体设计
我们使用 Electron 作为基础框架,利用其多窗口能力和主进程-渲染进程隔离特性,构建了一个双模式代码运行器:
- JavaScript 模式:通过 iframe 沙箱运行 HTML/JS 代码
- Python 模式:通过 Python 3.11 虚拟环境执行 Python 代码
核心模块
1. 窗口管理模块
javascript
// electron/windowManager.js
const { BrowserWindow } = require("electron");
const path = require("path");
function openCodeWin(options) {
const { initUrl, env, title, type, codeData } = options;
let win = new BrowserWindow({
useContentSize: true,
width: 610,
height: 450,
minWidth: 328,
minHeight: 450,
show: false,
title,
icon: path.join(__dirname, '../dist/logo-html.png'),
webPreferences: {
preload: path.join(__dirname, "../../electron/preload.js"),
webSecurity: true,
sandbox: false,
nodeIntegrationInWorker: true,
contextIsolation: true,
nodeIntegration: true,
partition: String(new Date().getTime()) // 隔离会话
},
});
// 窗口配置
win.setIcon(path.join(__dirname, "../../dist/logo-html.png"));
win.center();
win.setMenu(null);
// 根据代码类型加载不同页面
if(type === 'html') {
win.loadURL(initUrl + "#/htmlRunDrwer");
} else if(type === 'python') {
win.loadURL(initUrl + "#/pythonRunDrwer");
}
// 开发环境下打开开发者工具
if (env === "development" || env === "testing") {
win.webContents.openDevTools({ mode: "detach" });
}
// 防止标题被更改
win.on('page-title-updated', (event) => {
event.preventDefault();
});
// 页面加载完成后传递代码数据
win.webContents.on('did-finish-load', () => {
win.webContents.executeJavaScript(
`sessionStorage.setItem('codeData', ${JSON.stringify(codeData)});`
);
});
win.on("closed", () => {
win.removeAllListeners();
win = null;
});
}
module.exports = { openCodeWin };
2. JavaScript 代码运行器
vue
<!-- components/HtmlRunDrawer.vue -->
<template>
<iframe class="html-run-drawer" id="output"></iframe>
</template>
<script setup>
import { ref, nextTick, onMounted, onUnmounted } from "vue";
import { ElMessage } from "element-plus";
const codeData = ref("");
const loading = ref(false);
let loadingTimeout = null;
let isRunning = false;
// 从 sessionStorage 获取代码数据
const readSessionTimer = ref(null)
const getCode = () => {
const code = sessionStorage.getItem("codeData");
if(!code){
readSessionTimer.value = setTimeout(() => {
getCode()
}, 100);
}else {
handleRunButtonClick(code);
clearTimeout(readSessionTimer.value);
}
}
onMounted(() => {
getCode()
});
onUnmounted(() => {
loading.value = false;
isRunning = false;
clearTimeout(loadingTimeout);
})
const handleRunButtonClick = (code) => {
if (isRunning) return;
codeData.value = code;
OpenDrawer();
};
const OpenDrawer = () => {
if (isRunning) return;
isRunning = true;
loading.value = true;
// 设置超时保护
loadingTimeout = setTimeout(() => {
if (loading.value) {
ElMessage.warning("加载超时,请重新尝试");
loading.value = false;
isRunning = false;
}
}, 30000); // 30秒超时
nextTick(() => {
const iframe = document.getElementById("output");
if (iframe) {
// 使用 srcdoc 在沙箱中运行代码
iframe.srcdoc = codeData.value;
iframe.onload = () => {
clearTimeout(loadingTimeout);
loading.value = false;
isRunning = false;
};
} else {
console.error("iframe 元素未找到");
loading.value = false;
isRunning = false;
clearTimeout(loadingTimeout);
}
});
};
</script>
<style scoped>
.html-run-drawer{
width: 100vw;
height: 100vh;
border: none;
}
</style>
3. Python 代码运行器
vue
<!-- components/PythonRunDrawer.vue -->
<template>
<div class="python-run-drwer">
<div v-if="loading" class="loading-overlay">
<p class="loading_Text"></p>
</div>
<div v-if="ShowOutput" style="margin-bottom: 5px;margin-left: 5px;">输出:</div>
<div v-if="ShowErrorOutput" style="margin-bottom: 5px;margin-left: 5px;">报错:</div>
<div class="codeEditor_head">
<div v-if="ShowOutput">
<div class="output">{{ output }}</div>
</div>
<div v-if="ShowErrorOutput">
<div class="output">{{ errorOutput }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, nextTick, onMounted, onUnmounted } from "vue";
const codeData = ref("");
const output = ref("");
const errorOutput = ref("");
const ShowErrorOutput = ref(false);
const ShowOutput = ref(false);
const loading = ref(false);
onMounted(() => {
getCode()
});
const readSessionTimer = ref(null)
const getCode = () => {
const code = sessionStorage.getItem("codeData");
if(!code){
readSessionTimer.value = setTimeout(() => {
getCode()
}, 100);
}else {
handleRunButtonClick(code);
clearTimeout(readSessionTimer.value);
}
}
const handleRunButtonClick = (code) => {
codeData.value = code;
OpenPythonDrawer();
};
const OpenPythonDrawer = async () => {
loading.value = true;
nextTick(async () => {
try {
// 添加 UTF-8 编码支持
const fullCode = `import sys\nsys.stdout.reconfigure(encoding='utf-8')\n${codeData.value}`;
output.value = await window.electronAPI.runPython(fullCode);
ShowOutput.value = true;
} catch (error) {
ShowErrorOutput.value = true;
errorOutput.value = error;
} finally {
loading.value = false;
}
})
};
</script>
4. Python 执行器(主进程)
javascript
// electron/pythonRunner.js
const { ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');
const { exec } = require('child_process');
function setupPythonRunner() {
ipcMain.handle("run-python", async (event, code) => {
return new Promise((resolve, reject) => {
const exePath = path.join(outputDir, 'python.exe');
const tempFilePath = path.join(outputDir, 'temp_script.py');
// 创建临时文件
fs.writeFileSync(tempFilePath, code, "utf-8");
// 执行 Python 代码
exec(`"${exePath}" "${tempFilePath}"`, {
encoding: 'utf-8',
timeout: 30000 // 30秒超时
}, (error, stdout, stderr) => {
// 清理临时文件
try {
fs.unlinkSync(tempFilePath);
} catch (e) {
console.warn('临时文件清理失败:', e);
}
if (error) {
reject(stderr || error.message);
} else {
resolve(stdout);
}
});
});
});
}
module.exports = { setupPythonRunner };
安全考虑与最佳实践
1. 环境隔离
- 会话隔离 :使用
partition
为每个窗口创建独立的会话 - 进程隔离:利用 Electron 的进程模型,将代码执行与主应用隔离
- 文件系统隔离:Python 代码在临时文件中执行,执行后立即清理
2. 资源限制
- 超时控制:设置 30 秒执行超时,防止无限循环
- 内存限制:通过 iframe 沙箱限制 JavaScript 代码的资源访问
- 错误处理:完善的异常捕获和用户反馈
3. 用户体验优化
- 加载状态:提供清晰的加载指示器
- 错误展示:区分正常输出和错误信息
- 响应式设计:适配不同尺寸的代码运行结果
使用示例
运行 HTML/JavaScript 代码
示例
javascript
// 在渲染进程中调用
const code = `
<!DOCTYPE html>
<html>
<head>
<title>测试页面</title>
</head>
<body>
<h1>Hello World!</h1>
<script>
console.log('JavaScript 代码正常运行');
document.body.innerHTML += '<p>动态添加的内容</p>';
</script>
</body>
</html>
`;
window.electronAPI.openCodeWindow({
type: 'html',
codeData: code,
title: 'HTML 代码运行结果'
});
实际使用
AI回复
下面是一个使用 **纯 JavaScript + HTML5 Canvas** 编写的 **五子棋(Gomoku)** 游戏,支持双人对战、胜负判断、重新开始等功能,无需任何外部库。
---
### ✅ 特点:
- 黑白轮流下子(黑先)
- 自动检测五子连珠(横、竖、正斜、反斜)
- 胜利提示
- 点击"重新开始"按钮重置棋盘
- 响应式 Canvas 绘制
---
### 📄 完整代码(保存为 `gomoku.html`,直接用浏览器打开即可):
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>五子棋 - Gomoku</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f5f5dc;
font-family: "Microsoft YaHei", sans-serif;
}
...
运行效果

运行 Python 代码
示例
javascript
// 在渲染进程中调用
const pythonCode = `
for i in range(5):
print(f"当前数字: {i}")
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
print(f"5 的阶乘是: {factorial(5)}")
`;
window.electronAPI.openCodeWindow({
type: 'python',
codeData: pythonCode,
title: 'Python 代码运行结果'
});
实际使用
AI回复
下面是一个使用 Python 和 Pygame 编写的简单俄罗斯方块游戏。这个游戏包含了基本的俄罗斯方块功能:方块下落、左右移动、旋转、消除行、计分等。
---
### ✅ 环境要求:
- Python 3.x
- 安装 Pygame:`pip install pygame`
---
### 🧩 代码如下(保存为 `tetris.py`):
```python
import pygame
import random
import sys
# 初始化 pygame
pygame.init()
# 常量
SCREEN_WIDTH = 300
SCREEN_HEIGHT = 600
GRID_SIZE = 30
GRID_WIDTH = SCREEN_WIDTH // GRID_SIZE
GRID_HEIGHT = SCREEN_HEIGHT // GRID_SIZE
# 颜色定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
# 方块形状(7种经典俄罗斯方块)
SHAPES = [
[[1, 1, 1, 1]], # I
[[1, 1, 1], [0, 1, 0]], # T
[[1, 1, 1], [1, 0, 0]], # L
[[1, 1, 1], [0, 0, 1]], # J
[[1, 1], [1, 1]], # O
[[0, 1, 1], [1, 1, 0]], # S
[[1, 1, 0], [0, 1, 1]] # Z
]
# 对应颜色
COLORS = [CYAN, MAGENTA, ORANGE, BLUE, YELLOW, GREEN, RED]
# 创建窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("俄罗斯方块 - Tetris")
...
运行效果

总结
通过 Electron 构建的代码运行沙箱提供了以下优势:
- 安全性:在隔离环境中执行不可信代码
- 灵活性:支持多种编程语言
- 用户体验:实时反馈代码执行结果
- 稳定性:完善的错误处理和资源管理
这种架构特别适合需要验证 AI 生成代码的场景,为开发者提供了一个安全、可靠的代码验证环境。在实际项目中,还可以进一步扩展支持更多语言,增加代码分析功能,提升工具的实用性。