Compare commits

...

22 Commits

@ -0,0 +1,320 @@
# Ollama 本地AI模型部署指南
## 一、前提条件
1. ✅ 已安装 Ollama您已完成
2. ✅ 已下载并运行至少一个模型(如 llama2、qwen、mistral 等)
3. ✅ Ollama 服务正在运行(默认端口 11434
## 二、验证 Ollama 安装
### 1. 检查 Ollama 服务状态
打开命令行,运行:
```bash
# Windows PowerShell
curl http://localhost:11434/api/tags
# 或者使用浏览器访问
# http://localhost:11434/api/tags
```
如果返回模型列表,说明 Ollama 正常运行。
### 2. 测试模型是否可用
```bash
# 测试模型响应(使用 curl
curl http://localhost:11434/api/generate -d "{
\"model\": \"llama2\",
\"prompt\": \"Hello\",
\"stream\": false
}"
```
**注意**:将 `llama2` 替换为您实际下载的模型名称。
## 三、在系统中配置 Ollama
### 方法一通过Web界面配置推荐
1. **启动系统**
```bash
# 运行启动脚本
start_server.bat
```
2. **打开配置中心**
- 在浏览器中访问:`http://localhost:5000`
- 点击顶部导航栏的 **"配置中心"**
- 切换到 **"本地模型"** 标签页
3. **添加 Ollama 模型**
- 点击 **"上传模型"** 或 **"添加模型"** 按钮
- 填写以下信息:
- **模型名称**:输入您在 Ollama 中下载的模型名称(如 `llama2`、`qwen`、`mistral` 等)
- **模型描述**:可选,如 "Ollama本地模型 - Llama2"
- **API地址**`http://localhost:11434/v1`
- **重要**:必须是 `/v1` 结尾,这是 OpenAI 兼容格式的端点
- 点击 **"确认添加"**
4. **启用本地AI**
- 切换到 **"AI配置"** 标签页
- 勾选 **"使用本地AI模型"**
- 在下拉菜单中选择刚才添加的模型
- 点击 **"保存配置"**
5. **测试连接**
- 在本地模型列表中,找到您添加的模型
- 点击 **"测试连接"** 按钮
- 如果显示 "连接成功",说明配置正确
### 方法二:手动配置文件(高级)
如果Web界面无法使用可以手动编辑配置文件
1. **创建配置文件目录**(如果不存在)
```
config/
├── ai_config.json
└── local_models.json
```
2. **编辑 `config/local_models.json`**
```json
[
{
"id": "ollama-llama2",
"name": "llama2",
"description": "Ollama本地模型 - Llama2",
"apiUrl": "http://localhost:11434/v1",
"createdAt": "2025-01-XX"
}
]
```
3. **编辑 `config/ai_config.json`**
```json
{
"useLocalAi": true,
"selectedModelId": "ollama-llama2"
}
```
4. **重启服务器**
- 停止当前运行的服务器Ctrl+C
- 重新运行 `start_server.bat`
## 四、常见模型配置示例
### 1. Llama2
```json
{
"id": "ollama-llama2",
"name": "llama2",
"description": "Meta Llama2 模型",
"apiUrl": "http://localhost:11434/v1"
}
```
### 2. Qwen通义千问
```json
{
"id": "ollama-qwen",
"name": "qwen",
"description": "阿里通义千问模型",
"apiUrl": "http://localhost:11434/v1"
}
```
### 3. Mistral
```json
{
"id": "ollama-mistral",
"name": "mistral",
"description": "Mistral AI 模型",
"apiUrl": "http://localhost:11434/v1"
}
```
### 4. CodeLlama代码专用
```json
{
"id": "ollama-codellama",
"name": "codellama",
"description": "CodeLlama 代码生成模型",
"apiUrl": "http://localhost:11434/v1"
}
```
## 五、下载推荐的模型
对于代码质量分析,推荐使用以下模型:
### 1. CodeLlama推荐
```bash
ollama pull codellama
```
- **优点**:专门针对代码优化,理解代码结构更好
- **大小**:约 3.8GB7B参数版本
### 2. Qwen中文推荐
```bash
ollama pull qwen
```
- **优点**:中文理解能力强,适合中文项目
- **大小**:约 4.4GB7B参数版本
### 3. Llama2通用
```bash
ollama pull llama2
```
- **优点**:通用性强,性能稳定
- **大小**:约 3.8GB7B参数版本
### 4. Mistral高性能
```bash
ollama pull mistral
```
- **优点**:性能优秀,响应速度快
- **大小**:约 4.1GB7B参数版本
## 六、验证配置
### 1. 检查配置是否正确
启动服务器后,查看控制台输出,应该看到:
```
[AI分析] 使用提供商: 本地模型
[AI分析] 本地模型: llama2 (http://localhost:11434/v1)
```
### 2. 测试代码分析功能
1. 创建一个测试项目
2. 运行代码检查
3. 查看AI分析结果
如果AI分析正常工作说明配置成功。
## 七、常见问题排查
### 问题1连接失败
**症状**:测试连接时显示 "连接失败"
**解决方案**
1. 确认 Ollama 服务正在运行
```bash
# 检查进程Windows
tasklist | findstr ollama
```
2. 确认端口 11434 未被占用
```bash
# 检查端口Windows
netstat -ano | findstr 11434
```
3. 确认API地址正确`http://localhost:11434/v1`(注意 `/v1` 结尾)
4. 检查防火墙是否阻止了连接
### 问题2模型名称不匹配
**症状**:配置后无法使用,提示模型不存在
**解决方案**
1. 查看已下载的模型列表
```bash
ollama list
```
2. 确保配置中的模型名称与 Ollama 中的名称完全一致(区分大小写)
3. 如果模型名称不同,重新添加模型配置
### 问题3响应速度慢
**症状**AI分析需要很长时间
**解决方案**
1. 使用更小的模型(如 7B 参数版本)
2. 确保有足够的系统内存(建议至少 8GB
3. 如果使用 GPU确保 GPU 驱动正确安装
4. 考虑使用 CodeLlama 等针对代码优化的模型
### 问题4内存不足
**症状**Ollama 崩溃或响应超时
**解决方案**
1. 关闭其他占用内存的程序
2. 使用更小的模型3B 或 7B 参数)
3. 增加系统虚拟内存
4. 如果使用 GPU确保显存足够
## 八、性能优化建议
### 1. 选择合适的模型
- **代码分析**:推荐 CodeLlama 或 Qwen
- **中文项目**:推荐 Qwen
- **快速响应**:推荐 Mistral 或较小的模型
### 2. 系统要求
- **最低配置**8GB RAMCPU 模式
- **推荐配置**16GB RAMGPU 加速NVIDIA GPU
- **最佳配置**32GB+ RAM高端 GPU
### 3. GPU 加速(可选)
如果您的系统有 NVIDIA GPU可以启用 GPU 加速:
1. 安装 CUDA 和 cuDNN
2. Ollama 会自动检测并使用 GPU
3. 查看 GPU 使用情况:
```bash
# Windows
nvidia-smi
```
## 九、下一步
配置完成后,您可以:
1. ✅ 在代码检查中使用本地AI分析
2. ✅ 享受完全离线的代码质量分析
3. ✅ 保护代码隐私(数据不出本地)
4. ✅ 根据需要切换不同的模型
## 十、快速参考
### 常用命令
```bash
# 查看已安装的模型
ollama list
# 下载新模型
ollama pull <model-name>
# 运行模型测试
ollama run <model-name>
# 查看模型信息
ollama show <model-name>
```
### 配置文件位置
- AI配置`config/ai_config.json`
- 本地模型列表:`config/local_models.json`
### API端点
- Ollama API`http://localhost:11434/v1`
- OpenAI兼容端点`http://localhost:11434/v1/chat/completions`
---
**提示**:如果遇到问题,请检查服务器控制台的错误日志,通常会有详细的错误信息。

File diff suppressed because it is too large Load Diff

@ -0,0 +1,4 @@
{
"useLocalAi": false,
"selectedModelId": null
}

@ -0,0 +1,10 @@
[
{
"id": "model_1763556444768_81a9s6dh0",
"name": "qwen:7b",
"description": "测试",
"apiUrl": "http://localhost:11434/v1",
"createdAt": "2025-11-19T12:47:24.768Z",
"filePath": null
}
]

@ -398,6 +398,12 @@ body {
color: #999;
font-size: 12px;
margin-top: 5px;
transition: all 0.3s;
}
.result-location:hover {
transform: translateX(2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.result-actions {
@ -850,6 +856,27 @@ body {
background: white;
}
/* 模态框编辑器样式 */
#modalCodeEditor {
font-family: 'Courier New', 'Consolas', 'Monaco', monospace;
font-size: 14px;
line-height: 1.6;
tab-size: 4;
white-space: pre;
word-wrap: normal;
overflow-x: auto;
}
#modalCodeEditor:focus {
background: white;
box-shadow: inset 0 0 0 1px #1e3c72;
}
#modalCodeEditor::placeholder {
color: #999;
font-style: italic;
}
/* 成功按钮样式 */
.btn-success {
background: #28a745;
@ -1267,6 +1294,75 @@ input:checked + .slider:before {
padding: 20px;
}
/* 编辑器模态框特殊样式 */
#editorModal .modal-body {
padding: 0;
min-height: 0;
}
#editorModal .modal-content {
max-width: 1200px;
}
#editorModal .panel-header {
background: white;
border-bottom: 1px solid #e9ecef;
margin: 0;
}
#editorModal .file-info {
margin-top: 8px;
padding: 8px 12px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
#editorModal #modalSidebar {
background: white;
}
#editorModal .file-path {
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
font-family: 'Courier New', monospace;
font-size: 12px;
color: #666;
}
#editorModal #modalFileTree {
background: white;
padding: 8px;
}
#editorModal .file-item {
margin: 2px 0;
font-size: 13px;
}
#editorModal .file-item:hover {
background-color: #e3f2fd;
}
#editorModal .file-item.selected {
background-color: #1e3c72;
color: white;
}
#editorModal .file-item.selected .file-icon {
color: white;
}
/* 编辑模态框分栏与拖拽条 */
#modalResizer {
transition: background 0.1s;
}
#modalResizer:hover {
background: rgba(30,60,114,0.08);
}
.resizing #modalResizer {
background: rgba(30,60,114,0.15);
}
.modal-footer {
display: flex;
justify-content: flex-end;

@ -1,12 +1,14 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基于多agent协同的军工Python代码合规性检查系统 - FortifyCode</title>
<title>基于AI智能分析与多工具协同的Python代码质量检查系统 - FortifyCode</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 顶部导航栏 -->
<nav class="navbar">
@ -39,7 +41,7 @@
<div id="dashboard-page" class="page-content active">
<!-- 页面标题 -->
<div class="page-header fade-in">
<h1 class="page-title">基于多agent协同的军工Python代码合规性检查系统</h1>
<h1 class="page-title">基于AI智能分析与多工具协同的Python代码质量检查系统</h1>
<p class="page-subtitle">基于多 Agent 协同的智能代码安全检测平台</p>
</div>
@ -47,11 +49,11 @@
<div class="stats-grid fade-in">
<div class="stat-card">
<div class="stat-icon primary">
<i class="fas fa-code"></i>
<i class="fas fa-folder"></i>
</div>
<div class="stat-content">
<h3 id="totalFiles">1,247</h3>
<p>已检查文件</p>
<h3 id="totalProjects">0</h3>
<p>项目总数</p>
</div>
</div>
<div class="stat-card">
@ -59,7 +61,7 @@
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-content">
<h3 id="complianceRate">98.5%</h3>
<h3 id="complianceRate">0%</h3>
<p>合规率</p>
</div>
</div>
@ -68,7 +70,7 @@
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="stat-content">
<h3 id="pendingIssues">23</h3>
<h3 id="pendingIssues">0</h3>
<p>待修复问题</p>
</div>
</div>
@ -77,94 +79,318 @@
<i class="fas fa-bug"></i>
</div>
<div class="stat-content">
<h3 id="highRiskIssues">5</h3>
<h3 id="highRiskIssues">0</h3>
<p>高危漏洞</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon primary">
<i class="fas fa-file-code"></i>
</div>
<div class="stat-content">
<h3 id="totalFiles">0</h3>
<p>已检查文件</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-tasks"></i>
</div>
<div class="stat-content">
<h3 id="checkedProjects">0</h3>
<p>已检查项目</p>
</div>
</div>
</div>
<!-- 功能区域 -->
<div class="function-grid fade-in">
<!-- 代码上传和检查区域 -->
<div class="function-grid fade-in" style="grid-template-columns: 1fr 1fr;">
<!-- 快速开始指南 -->
<div class="main-panel">
<div class="panel-header">
<h2 class="panel-title">快捷代码检查</h2>
<div class="filter-tabs">
<span class="filter-tab active">Python</span>
</div>
<h2 class="panel-title"><i class="fas fa-rocket"></i> 快速开始</h2>
</div>
<div class="panel-content">
<div class="upload-area" id="uploadArea">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
<div style="line-height: 2;">
<div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #1e3c72;">
<strong><i class="fas fa-folder-open"></i> 1. 导入项目</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">进入"项目管理"页面,点击"导入文件夹为项目"按钮选择您的Python项目文件夹。</p>
</div>
<div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #28a745;">
<strong><i class="fas fa-play"></i> 2. 运行检查</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">在项目详情页点击"运行检查"按钮,系统将使用 pylint、flake8、bandit 进行代码质量检查。</p>
</div>
<div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #ffc107;">
<strong><i class="fas fa-search"></i> 3. 查看结果</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">检查完成后,在"检查结果详情"面板查看问题列表,点击"定位"按钮可跳转到问题代码位置。</p>
</div>
<div style="padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #17a2b8;">
<strong><i class="fas fa-edit"></i> 4. 编辑修复</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">使用"文件编辑"功能在线修改代码,保存后重新运行检查验证修复效果。</p>
</div>
<div class="upload-text">拖拽文件到此处或点击上传</div>
<div class="upload-hint">支持 .py, .pyx, .pyi 文件,最大 100MB</div>
<input type="file" class="file-input" id="fileInput" multiple accept=".py,.pyx,.pyi">
</div>
<div class="progress-container" id="progressContainer" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%;"></div>
</div>
</div>
<!-- 系统功能说明 -->
<div class="main-panel">
<div class="panel-header">
<h2 class="panel-title"><i class="fas fa-info-circle"></i> 系统功能</h2>
</div>
<div class="panel-content">
<div style="line-height: 2;">
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>多工具协同检查</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">集成 pylint、flake8、bandit 三大工具,全面检测代码质量和安全问题</p>
</div>
</div>
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>自定义规则集</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">支持上传 setup.cfg、.pylintrc、.flake8 等配置文件,灵活定制检查规则</p>
</div>
</div>
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>在线代码编辑</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">内置代码编辑器,支持文件浏览、编辑、保存,快速修复问题</p>
</div>
</div>
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>精确定位问题</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">检查结果精确到文件、行、列,一键定位到问题代码位置</p>
</div>
</div>
<div style="display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>项目历史管理</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">记录每次检查结果,追踪代码质量变化趋势</p>
</div>
</div>
<div class="progress-text" id="progressText">准备检查...</div>
</div>
</div>
</div>
</div>
<div style="margin-top: 20px; text-align: center;">
<button class="btn btn-primary" id="startCheckBtn" style="display: none;">
<i class="fas fa-play"></i> 开始检查
</button>
<button class="btn btn-secondary" id="stopCheckBtn" style="display: none;">
<i class="fas fa-stop"></i> 停止检查
</button>
<!-- 最近项目列表 -->
<div class="main-panel fade-in" style="margin-top: 20px;">
<div class="panel-header">
<h2 class="panel-title"><i class="fas fa-history"></i> 最近项目</h2>
<a href="#projects" class="btn btn-secondary" style="text-decoration: none; padding: 6px 12px; font-size: 12px;">查看全部</a>
</div>
<div class="panel-content">
<div id="recentProjectsList" style="min-height: 100px;">
<div style="text-align: center; padding: 40px; color: #999;">
<i class="fas fa-folder-open" style="font-size: 48px; margin-bottom: 15px; opacity: 0.3;"></i>
<p>暂无项目,请先导入项目</p>
</div>
</div>
</div>
</div>
</div>
<!-- 规则集管理页面 -->
<div id="rules-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">规则集管理</h1>
<p class="page-subtitle">上传、查看、编辑与删除自定义规则配置pylint/flake8 等)</p>
</div>
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-primary" id="uploadRuleBtn">
<i class="fas fa-upload"></i> 上传规则文件
</button>
<button class="btn btn-secondary" id="refreshRuleBtn">
<i class="fas fa-rotate"></i> 刷新
</button>
</div>
</div>
<!-- 多Agent状态面板 -->
<div class="agent-status">
<div class="panel-header">
<h2 class="panel-title">Agent 状态</h2>
</div>
<div class="agent-list">
<div class="agent-item">
<div class="agent-avatar">A1</div>
<div class="agent-info">
<div class="agent-name">pylint Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
<div class="projects-grid" id="rulesList">
<!-- 规则项通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 编辑规则文件模态框 -->
<div id="editRuleModal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 800px; width: 90%;">
<div class="modal-header">
<h2 id="editRuleTitle">编辑规则</h2>
<span class="close" id="closeEditRule">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label>内容</label>
<textarea id="editRuleTextarea" rows="18" style="width: 100%;"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelEditRule">取消</button>
<button type="button" class="btn btn-success" id="saveRuleBtn"><i class="fas fa-save"></i>
保存</button>
</div>
</div>
</div>
<!-- 配置中心页面 -->
<div id="settings-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">配置中心</h1>
<p class="page-subtitle">管理系统配置和AI模型设置</p>
</div>
<div class="settings-container">
<div class="settings-tabs">
<div class="settings-tab active" data-tab="ai-config">
<i class="fas fa-robot"></i> AI配置
</div>
<div class="settings-tab" data-tab="local-models">
<i class="fas fa-server"></i> 本地模型
</div>
</div>
<div class="settings-content">
<!-- AI配置面板 -->
<div class="settings-panel active" id="ai-config-panel">
<div class="settings-section">
<h3><i class="fas fa-cog"></i> AI分析配置</h3>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="useLocalAi" style="width: auto; margin: 0; cursor: pointer;">
<span style="font-weight: 500;">使用本地AI模型</span>
</label>
<p style="margin-top: 8px; font-size: 12px; color: #666; line-height: 1.5;">
启用后系统将使用本地部署的AI模型进行分析而不是云端API。需要先上传并配置本地模型。
</p>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A2</div>
<div class="agent-info">
<div class="agent-name">flake8 Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
<div id="localModelSelectGroup" style="display: none; margin-top: 20px;">
<div class="form-group">
<label for="localModelSelect" style="font-weight: 500; margin-bottom: 8px; display: block;">
<i class="fas fa-list"></i> 选择本地模型
</label>
<select id="localModelSelect" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; background: white; cursor: pointer;">
<option value="">请选择本地模型...</option>
</select>
<p style="margin-top: 8px; font-size: 12px; color: #666; line-height: 1.5;">
<i class="fas fa-info-circle" style="color: #1e3c72; margin-right: 4px;"></i>
选择已上传的本地AI模型。如果列表为空请先上传模型。
</p>
</div>
</div>
<div id="cloudAiConfigGroup" style="margin-top: 20px;">
<div class="form-group">
<label style="font-weight: 500; margin-bottom: 12px; display: block;">
<i class="fas fa-cloud"></i> 云端AI配置
</label>
<div style="padding: 12px; background: #f8f9fa; border-radius: 6px; border: 1px solid #e9ecef;">
<p style="margin: 0 0 10px 0; font-size: 13px; color: #666;">
当前使用的云端AI服务<strong id="currentCloudProvider">未配置</strong>
</p>
<p style="margin: 0; font-size: 12px; color: #999;">
云端AI配置通过环境变量设置请修改启动脚本中的环境变量。
</p>
</div>
</div>
</div>
<div class="settings-actions" style="margin-top: 30px;">
<button type="button" class="btn btn-primary" id="saveAiConfigBtn">
<i class="fas fa-save"></i> 保存配置
</button>
<button type="button" class="btn btn-secondary" id="testAiConfigBtn">
<i class="fas fa-vial"></i> 测试连接
</button>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A3</div>
<div class="agent-info">
<div class="agent-name">bandit Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
<!-- 本地模型管理面板 -->
<div class="settings-panel" id="local-models-panel">
<div class="settings-section">
<h3><i class="fas fa-server"></i> 本地模型管理</h3>
<div style="margin-bottom: 20px;">
<button type="button" class="btn btn-primary" id="uploadModelBtn">
<i class="fas fa-upload"></i> 上传模型
</button>
<button type="button" class="btn btn-secondary" id="refreshModelsBtn">
<i class="fas fa-rotate"></i> 刷新列表
</button>
</div>
<div id="localModelsList" style="min-height: 200px;">
<div style="text-align: center; padding: 40px; color: #999;">
<i class="fas fa-server" style="font-size: 48px; margin-bottom: 15px; opacity: 0.3;"></i>
<p>暂无本地模型,请先上传模型</p>
</div>
</div>
<div style="margin-top: 20px; padding: 15px; background: #f0f7ff; border-radius: 6px; border: 1px solid #b3d9ff;">
<h4 style="margin: 0 0 10px 0; font-size: 14px; color: #1e3c72;">
<i class="fas fa-info-circle"></i> 使用说明
</h4>
<ul style="margin: 0; padding-left: 20px; font-size: 12px; color: #666; line-height: 1.8;">
<li>本地模型需要部署Ollama服务推荐或兼容OpenAI API的本地服务</li>
<li>上传的模型文件将存储在本地,确保有足够的磁盘空间</li>
<li>支持的模型格式Ollama模型、GGUF格式等</li>
<li>模型文件较大,上传可能需要较长时间</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 检查结果区域 -->
<div class="results-section fade-in" id="resultsSection" style="display: none;">
<div class="results-header">
<h2 class="results-title">检查结果</h2>
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
<!-- 上传模型模态框 -->
<div id="uploadModelModal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">
<h2>上传本地AI模型</h2>
<span class="close" id="closeUploadModelModal">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="modelName">模型名称 *</label>
<input type="text" id="modelName" name="modelName" required placeholder="例如: llama2, qwen, mistral">
</div>
<div class="form-group">
<label for="modelDescription">模型描述</label>
<textarea id="modelDescription" name="modelDescription" rows="3" placeholder="描述模型的用途和特点"></textarea>
</div>
<div class="form-group">
<label for="modelApiUrl">API地址 *</label>
<input type="text" id="modelApiUrl" name="modelApiUrl" required placeholder="http://localhost:11434/v1" value="http://localhost:11434/v1">
<p style="margin-top: 8px; font-size: 12px; color: #666;">
本地AI服务的API地址。Ollama默认地址http://localhost:11434/v1<br>
<strong>注意</strong>:系统会自动添加 /chat/completions 端点,您只需填写基础地址即可。
</p>
</div>
<div class="form-group">
<label for="modelFile">模型文件(可选)</label>
<input type="file" id="modelFile" name="modelFile" accept=".gguf,.bin,.safetensors">
<p style="margin-top: 8px; font-size: 12px; color: #666;">
如果使用Ollama模型文件会自动从Ollama服务加载无需上传。如果使用其他服务可以上传模型文件。
</p>
</div>
</div>
<div class="results-content" id="resultsContent">
<!-- 结果项将通过 JavaScript 动态生成 -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelUploadModel">取消</button>
<button type="button" class="btn btn-primary" id="confirmUploadModel">
<i class="fas fa-upload"></i> 上传
</button>
</div>
</div>
</div>
@ -179,8 +405,8 @@
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-primary" id="createProjectBtn">
<i class="fas fa-plus"></i> 新建项目
<button class="btn btn-secondary" id="importFolderProjectBtn">
<i class="fas fa-folder-open"></i> 导入文件夹为项目
</button>
</div>
<div class="projects-search">
@ -217,8 +443,8 @@
<button class="btn btn-primary" id="runCheckBtn">
<i class="fas fa-play"></i> 运行检查
</button>
<button class="btn btn-secondary" id="editProjectBtn">
<i class="fas fa-edit"></i> 编辑项目
<button class="btn btn-secondary" id="openEditorModalBtn">
<i class="fas fa-pen"></i> 文件编辑
</button>
<button class="btn btn-danger" id="deleteProjectBtn">
<i class="fas fa-trash"></i> 删除项目
@ -229,77 +455,22 @@
<!-- 项目信息将通过 JavaScript 动态生成 -->
</div>
</div>
<div class="check-history-panel">
<div class="panel-header">
<h2 class="panel-title">检查历史</h2>
</div>
<div class="panel-content" id="checkHistoryContent">
<!-- 检查历史将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 文件浏览器面板 -->
<div class="file-browser-panel">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 15px;">
<h2 class="panel-title">文件浏览器</h2>
<div class="file-actions">
<button class="btn btn-primary" id="uploadFileBtn">
<i class="fas fa-upload"></i> 上传文件
</button>
<button class="btn btn-secondary" id="createFileBtn">
<i class="fas fa-plus"></i> 新建文件
</button>
<button class="btn btn-secondary" id="createFolderBtn">
<i class="fas fa-folder-plus"></i> 新建文件夹
</button>
</div>
</div>
<div class="file-path">
<span id="currentPath">/</span>
</div>
</div>
<div class="file-browser-content">
<div class="file-tree" id="fileTree">
<!-- 文件树将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 代码编辑器面板 -->
<div class="code-editor-panel" id="codeEditorPanel" style="display: none;">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 15px;">
<h2 class="panel-title" id="editorTitle">代码编辑器</h2>
<div class="editor-actions">
<button class="btn btn-success" id="saveFileBtn">
<i class="fas fa-save"></i> 保存
</button>
<button class="btn btn-secondary" id="closeEditorBtn">
<i class="fas fa-times"></i> 关闭
</button>
</div>
</div>
<div class="file-info">
<span id="currentFilePath">未选择文件</span>
</div>
</div>
<div class="editor-content">
<textarea id="codeEditor" placeholder="选择文件开始编辑..."></textarea>
</div>
</div>
<!-- 检查结果详情 -->
<div class="check-results-panel" id="checkResultsPanel" style="display: none;">
<div class="panel-header">
<h2 class="panel-title">检查结果详情</h2>
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
<div style="display: flex; align-items: center; gap: 10px;">
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
</div>
<button id="downloadReportBtn" class="btn btn-primary" style="margin-left: auto;">
<i class="fas fa-download"></i> 下载报告
</button>
</div>
</div>
<div class="panel-content" id="checkResultsContent">
@ -331,17 +502,18 @@
<div class="source-type-tabs">
<input type="radio" id="sourceGithub" name="source_type" value="github" checked>
<label for="sourceGithub">GitHub</label>
<input type="radio" id="sourceGitee" name="source_type" value="gitee">
<label for="sourceGitee">Gitee</label>
<input type="radio" id="sourceUpload" name="source_type" value="upload">
<label for="sourceUpload">文件上传</label>
</div>
</div>
<div class="form-group" id="gitUrlGroup">
<label for="gitUrl">Git URL *</label>
<input type="url" id="gitUrl" name="source_url" placeholder="https://github.com/username/repository.git">
<input type="url" id="gitUrl" name="source_url"
placeholder="https://github.com/username/repository.git">
</div>
<div class="form-group" id="fileUploadGroup" style="display: none;">
<label for="fileUpload">选择文件或文件夹</label>
@ -356,6 +528,133 @@
</div>
</div>
<!-- 文件编辑模态框 -->
<div id="editorModal" class="modal" style="display: none;">
<div class="modal-content"
style="max-width: 1000px; width: 95%; height: 80vh; display: flex; flex-direction: column;">
<div class="modal-header">
<h2>项目文件编辑</h2>
<span class="close" id="closeEditorModal">&times;</span>
</div>
<div class="modal-body" style="flex:1; display:flex; overflow:hidden;">
<div id="modalSidebar" style="width: 320px; min-width: 220px; max-width: 60%; border-right: 1px solid #eee; display:flex; flex-direction:column;">
<div class="file-path" style="padding:8px 12px;">
<span id="modalCurrentPath">/</span>
</div>
<div class="file-tree" id="modalFileTree" style="flex:1; overflow:auto;"></div>
</div>
<div id="modalResizer" style="width:6px; cursor: col-resize; background: transparent; position: relative;">
<div style="position:absolute; top:0; bottom:0; left:2px; width:2px; background:#e5e7eb;"></div>
</div>
<div style="flex:1; display:flex; flex-direction:column;">
<div class="panel-header" style="padding:8px 12px;">
<div style="display:flex; align-items:center; gap:10px;">
<h3 class="panel-title" id="modalEditorTitle" style="margin:0;">代码编辑器</h3>
<div class="editor-actions" style="margin-left:auto; display:flex; gap:8px;">
<button class="btn btn-success" id="modalSaveFileBtn"><i class="fas fa-save"></i>
保存</button>
<button class="btn" id="modalCloseBtn"><i class="fas fa-times"></i> 关闭</button>
</div>
</div>
<div class="file-info"><span id="modalCurrentFilePath">未选择文件</span></div>
</div>
<div class="editor-content" style="flex:1; padding:0; position:relative; overflow:hidden; display:flex; flex-direction:column;">
<div id="codeEditorWrapper" style="flex:1; display:flex; overflow:hidden; position:relative; height:100%; align-items:stretch;">
<div id="lineNumbers" style="background:#f5f5f5; color:#999; padding:15px 8px 15px 15px; font-family:'Courier New', monospace; font-size:14px; line-height:1.6; text-align:right; user-select:none; border-right:1px solid #e0e0e0; min-width:50px; width:50px; overflow-y:scroll; overflow-x:hidden; flex-shrink:0;"></div>
<textarea id="modalCodeEditor" placeholder="选择文件开始编辑..." style="flex:1; width:100%; border:none; padding:15px 15px 15px 8px; font-family:'Courier New', monospace; font-size:14px; line-height:1.6; resize:none; outline:none; background:#fafafa; color:#333; tab-size:4; overflow-y:scroll; overflow-x:auto;"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 规则集选择对话框 -->
<div id="ruleSetSelectModal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h2>检查配置</h2>
<span class="close" id="closeRuleSetModal">&times;</span>
</div>
<div class="modal-body">
<p style="margin-bottom: 20px; color: #666; font-size: 14px;">
<i class="fas fa-info-circle" style="color: #1e3c72; margin-right: 8px;"></i>
选择检查工具和规则集配置。至少需要选择一个检查工具。
</p>
<!-- 工具选择 -->
<div class="form-group">
<label style="font-weight: 500; margin-bottom: 12px; display: block;">
<i class="fas fa-tools" style="color: #1e3c72; margin-right: 8px;"></i>
选择检查工具
</label>
<div style="display: flex; flex-direction: column; gap: 10px; padding: 12px; background: #f8f9fa; border-radius: 6px; border: 1px solid #e9ecef;">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="toolPylint" class="tool-checkbox" value="pylint" checked style="width: auto; margin: 0; cursor: pointer;">
<span style="flex: 1;">
<strong>Pylint</strong>
<span style="color: #666; font-size: 12px; margin-left: 8px;">代码质量检查</span>
</span>
</label>
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="toolFlake8" class="tool-checkbox" value="flake8" checked style="width: auto; margin: 0; cursor: pointer;">
<span style="flex: 1;">
<strong>Flake8</strong>
<span style="color: #666; font-size: 12px; margin-left: 8px;">代码风格检查</span>
</span>
</label>
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="toolBandit" class="tool-checkbox" value="bandit" checked style="width: auto; margin: 0; cursor: pointer;">
<span style="flex: 1;">
<strong>Bandit</strong>
<span style="color: #666; font-size: 12px; margin-left: 8px;">安全漏洞检查</span>
</span>
</label>
</div>
</div>
<!-- 规则集选择 -->
<div class="form-group" style="margin-top: 20px;">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; padding: 12px; background: #f8f9fa; border-radius: 6px; border: 1px solid #e9ecef;">
<input type="checkbox" id="useCustomRuleSet" style="width: auto; margin: 0; cursor: pointer;">
<span style="font-weight: 500;">使用自定义规则集</span>
</label>
</div>
<div class="form-group" id="ruleSetSelectGroup" style="display: none; margin-top: 15px;">
<label for="ruleSetSelect" style="font-weight: 500; margin-bottom: 8px; display: block;">选择规则集</label>
<select id="ruleSetSelect" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; background: white; cursor: pointer;">
<option value="">请选择规则集...</option>
</select>
<p style="margin-top: 8px; font-size: 12px; color: #666; line-height: 1.5;">
<i class="fas fa-check-circle" style="color: #28a745; margin-right: 4px;"></i>
选中的规则集文件将应用于已选择的检查工具
</p>
</div>
<!-- AI分析选项 -->
<div class="form-group" style="margin-top: 20px; padding: 12px; background: #f0f7ff; border-radius: 6px; border: 1px solid #b3d9ff;">
<label style="display: flex; align-items: start; gap: 10px; cursor: pointer;">
<input type="checkbox" id="useAiAnalysis" checked style="width: auto; margin: 0; cursor: pointer; margin-top: 2px;">
<div style="flex: 1;">
<span style="font-weight: 500; display: flex; align-items: center; gap: 6px;">
<i class="fas fa-robot" style="color: #1e3c72;"></i>
启用AI智能分析
</span>
<p style="margin: 6px 0 0 0; font-size: 12px; color: #666; line-height: 1.5;">
自动去重、风险评估和生成修改建议。需要配置OPENAI_API_KEY环境变量才能使用完整AI功能。
</p>
</div>
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelRuleSetBtn">取消</button>
<button type="button" class="btn btn-primary" id="confirmRuleSetBtn">开始检查</button>
</div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,202 @@
const API_BASE_URL = window.location.origin + '/api';
function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function showSuccessMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `position: fixed; top: 20px; right: 20px; background: #d4edda; color: #155724; border: 1px solid #c3e6cb; border-radius: 6px; padding: 15px 20px; z-index: 3000; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px;`;
messageDiv.innerHTML = `<i class="fas fa-check-circle"></i><span>${message}</span>`;
document.body.appendChild(messageDiv);
setTimeout(() => messageDiv.remove(), 3000);
}
function showErrorMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `position: fixed; top: 20px; right: 20px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; border-radius: 6px; padding: 15px 20px; z-index: 3000; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px;`;
messageDiv.innerHTML = `<i class="fas fa-exclamation-circle"></i><span>${message}</span>`;
document.body.appendChild(messageDiv);
setTimeout(() => messageDiv.remove(), 5000);
}
async function loadRules() {
try {
const response = await fetch(`${API_BASE_URL}/rules`);
const data = await response.json();
if (!data.success) throw new Error(data.error || '加载失败');
renderRulesList(data.data || []);
} catch (e) {
console.error('加载规则失败:', e);
showErrorMessage('加载规则失败');
}
}
function renderRulesList(files) {
const container = document.getElementById('rulesList');
if (!container) return;
container.innerHTML = '';
if (!files.length) {
container.innerHTML = '<div style="text-align:center; padding: 20px; color:#666;">暂无规则文件</div>';
return;
}
files.forEach(f => {
const card = document.createElement('div');
card.className = 'project-card';
const size = formatFileSize(f.size || 0);
card.innerHTML = `
<div class="project-header">
<div>
<div class="project-title">${f.name}</div>
<div class="project-description">大小: ${size} | 更新: ${new Date(f.modified_at).toLocaleString()}</div>
</div>
</div>
<div class="project-actions">
<button class="btn btn-secondary">编辑</button>
<button class="btn btn-primary">下载</button>
<button class="btn btn-danger">删除</button>
</div>
`;
const [editBtn, downloadBtn, deleteBtn] = card.querySelectorAll('.project-actions .btn');
editBtn.addEventListener('click', (e) => { e.stopPropagation(); editRule(f.name); });
downloadBtn.addEventListener('click', (e) => { e.stopPropagation(); downloadRule(f.name); });
deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); deleteRule(f.name); });
container.appendChild(card);
});
}
function showRuleUploadDialog() {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = '.cfg,.ini,.toml,.yaml,.yml,.json,.flake8,.pylintrc,setup.cfg,pyproject.toml,tox.ini';
input.onchange = async (e) => {
const files = Array.from(e.target.files || []);
if (!files.length) return;
const formData = new FormData();
files.forEach(f => formData.append('files', f));
try {
const resp = await fetch(`${API_BASE_URL}/rules/upload`, { method: 'POST', body: formData });
const data = await resp.json();
if (data.success) {
showSuccessMessage('规则上传成功');
loadRules();
} else {
showErrorMessage('规则上传失败:' + (data.error || ''));
}
} catch (e) {
console.error('上传规则失败:', e);
showErrorMessage('上传规则失败');
}
};
input.click();
}
let currentEditingRule = '';
async function editRule(name) {
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`);
const data = await resp.json();
if (!data.success) throw new Error(data.error || '读取失败');
currentEditingRule = name;
document.getElementById('editRuleTitle').textContent = `编辑规则 - ${name}`;
document.getElementById('editRuleTextarea').value = data.data.content || '';
document.getElementById('editRuleModal').style.display = 'block';
} catch (e) {
console.error('读取规则失败:', e);
showErrorMessage('读取规则失败');
}
}
function hideEditRuleModal() {
const modal = document.getElementById('editRuleModal');
if (modal) modal.style.display = 'none';
currentEditingRule = '';
}
async function saveEditingRule() {
if (!currentEditingRule) return;
const content = document.getElementById('editRuleTextarea').value;
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(currentEditingRule)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
const data = await resp.json();
if (data.success) {
showSuccessMessage('保存成功');
hideEditRuleModal();
loadRules();
} else {
showErrorMessage('保存失败:' + (data.error || ''));
}
} catch (e) {
console.error('保存规则失败:', e);
showErrorMessage('保存失败');
}
}
async function deleteRule(name) {
if (!confirm(`确定删除规则文件 "${name}" 吗?`)) return;
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`, { method: 'DELETE' });
const data = await resp.json();
if (data.success) {
showSuccessMessage('删除成功');
loadRules();
} else {
showErrorMessage('删除失败:' + (data.error || ''));
}
} catch (e) {
console.error('删除规则失败:', e);
showErrorMessage('删除失败');
}
}
async function downloadRule(name) {
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`);
const data = await resp.json();
if (!data.success) throw new Error(data.error || '下载失败');
const blob = new Blob([data.data.content || ''], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
a.remove();
} catch (e) {
console.error('下载规则失败:', e);
showErrorMessage('下载失败');
}
}
function bindRulesEvents() {
const uploadRuleBtn = document.getElementById('uploadRuleBtn');
const refreshRuleBtn = document.getElementById('refreshRuleBtn');
const closeEditRule = document.getElementById('closeEditRule');
const cancelEditRule = document.getElementById('cancelEditRule');
const saveRuleBtn = document.getElementById('saveRuleBtn');
if (uploadRuleBtn) uploadRuleBtn.addEventListener('click', showRuleUploadDialog);
if (refreshRuleBtn) refreshRuleBtn.addEventListener('click', loadRules);
if (closeEditRule) closeEditRule.addEventListener('click', hideEditRuleModal);
if (cancelEditRule) cancelEditRule.addEventListener('click', hideEditRuleModal);
if (saveRuleBtn) saveRuleBtn.addEventListener('click', saveEditingRule);
}
document.addEventListener('DOMContentLoaded', () => {
bindRulesEvents();
});
export { loadRules };

@ -1,7 +1,7 @@
{
"name": "fortifycode",
"version": "1.0.0",
"lockfileVersion": 3,
"lockfileVersion": 2,
"requires": true,
"packages": {
"node_modules/accepts": {
@ -931,6 +931,26 @@
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",

@ -1,48 +0,0 @@
# 代码质量检查报告
**文件:** test_sample.py
**检查时间:** 2025/9/18 14:28:54
## flake8 检查结果
### 输出:
```
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:3:1: E302 expected 2 blank lines, found 1
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:5:37: E261 at least two spaces before inline comment
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:7:1: E305 expected 2 blank lines after class or function definition, found 1
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:7:12: E225 missing whitespace around operator
```
### 解析结果:
```json
[
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 3,
"column": 1,
"code": "E302",
"message": "expected 2 blank lines, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 5,
"column": 37,
"code": "E261",
"message": "at least two spaces before inline comment"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 7,
"column": 1,
"code": "E305",
"message": "expected 2 blank lines after class or function definition, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 7,
"column": 12,
"code": "E225",
"message": "missing whitespace around operator"
}
]
```

@ -1,370 +0,0 @@
# 代码质量检查报告
**文件:** test_sample.py
**检查时间:** 2025/9/18 14:33:21
## bandit 检查结果
### 输出:
```
{
"errors": [],
"generated_at": "2025-09-18T06:33:19Z",
"metrics": {
"C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
### 错误:
```
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
```
### 解析结果:
```json
{
"errors": [],
"generated_at": "2025-09-18T06:33:19Z",
"metrics": {
"C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
## flake8 检查结果
### 输出:
```
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:3:1: E302 expected 2 blank lines, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:5:37: E261 at least two spaces before inline comment
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:7:1: E305 expected 2 blank lines after class or function definition, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:7:12: E225 missing whitespace around operator
```
### 解析结果:
```json
[
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 3,
"column": 1,
"code": "E302",
"message": "expected 2 blank lines, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 5,
"column": 37,
"code": "E261",
"message": "at least two spaces before inline comment"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 7,
"column": 1,
"code": "E305",
"message": "expected 2 blank lines after class or function definition, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 7,
"column": 12,
"code": "E225",
"message": "missing whitespace around operator"
}
]
```
## pylint 检查结果
### 输出:
```
[
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177199344-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```
### 解析结果:
```json
[
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177199344-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```

@ -1,370 +0,0 @@
# 代码质量检查报告
**文件:** test_sample.py
**检查时间:** 2025/9/18 14:35:53
## bandit 检查结果
### 输出:
```
{
"errors": [],
"generated_at": "2025-09-18T06:35:52Z",
"metrics": {
"C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
### 错误:
```
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
```
### 解析结果:
```json
{
"errors": [],
"generated_at": "2025-09-18T06:35:52Z",
"metrics": {
"C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
## flake8 检查结果
### 输出:
```
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:3:1: E302 expected 2 blank lines, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:5:37: E261 at least two spaces before inline comment
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:7:1: E305 expected 2 blank lines after class or function definition, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:7:12: E225 missing whitespace around operator
```
### 解析结果:
```json
[
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 3,
"column": 1,
"code": "E302",
"message": "expected 2 blank lines, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 5,
"column": 37,
"code": "E261",
"message": "at least two spaces before inline comment"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 7,
"column": 1,
"code": "E305",
"message": "expected 2 blank lines after class or function definition, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 7,
"column": 12,
"code": "E225",
"message": "missing whitespace around operator"
}
]
```
## pylint 检查结果
### 输出:
```
[
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177351895-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```
### 解析结果:
```json
[
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177351895-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```

@ -11,7 +11,8 @@
"dependencies": {
"cors": "^2.8.5",
"express": "^5.1.0",
"multer": "^2.0.2"
"multer": "^2.0.2",
"ws": "^8.18.3"
}
},
"node_modules/accepts": {
@ -941,6 +942,26 @@
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
@ -1610,6 +1631,12 @@
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
"version": "8.18.3",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"requires": {}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",

@ -6,7 +6,9 @@
"scripts": {
"start": "node backend.js",
"dev": "node backend.js",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"cleanup:dry": "node scripts/cleanup.js",
"cleanup": "node scripts/cleanup.js --execute"
},
"keywords": [
"code-quality",

@ -0,0 +1,62 @@
[flake8]
# 基础规则配置
# 启用的检查规则集(默认包含 pycodestyle (E/W)、pyflakes (F),扩展支持 bugbear (B)、docstrings (D) 等)
select = E, W, F, B, D
# 忽略的特定错误/警告代码(根据团队习惯调整)
ignore =
# 行长度超过限制默认79下方已调整为120故忽略默认E501
E501,
# 与 black 格式化工具冲突的缩进检查black 推荐忽略)
E203,
# 多余的空白行警告(允许适当空行提高可读性)
W293,
# 变量未使用警告(临时变量或调试代码可能需要)
F841,
# bugbear 中过于严格的"表达式复杂度"警告(根据业务调整)
B019,
# 文档字符串相关:忽略模块级文档缺失(小型脚本可能不需要)
D100,
# 文档字符串相关:忽略私有函数文档缺失(内部函数可简化)
D105
# 最大行长度默认79调整为120更适合现代屏幕
max-line-length = 120
# 循环复杂度最大值(超过此值提示代码可能过于复杂,需拆分)
max-complexity = 12
# 排除不需要检查的文件/目录(如虚拟环境、自动生成的代码)
exclude =
.git,
__pycache__,
venv,
.venv,
migrations,
build,
dist,
*.egg-info
# 针对特定文件的单独忽略规则(例如测试文件放宽检查)
per-file-ignores =
# 测试文件忽略"函数参数未使用"(测试用例可能有占位参数)
tests/*.py: F841,
# 配置文件忽略"模块文档缺失"(配置文件通常无需文档)
config/*.py: D100
# 以下为插件扩展配置(需提前安装对应插件)
# 1. flake8-docstrings文档字符串检查需安装pip install flake8-docstrings
[flake8-docstrings]
# 文档字符串风格(支持 google/numpy/restructuredtext
docstring-style = google
# 允许无返回值函数省略 @return 说明
ignore-missing-docstrings = False # 全局不允许缺失文档字符串(除非在 ignore 中单独排除)
# 忽略"文档字符串末尾无空行"的警告(部分团队不强制)
ignore-decorators = abc.abstractmethod # 抽象方法无需重复文档
# 2. flake8-bugbear增强错误检查需安装pip install flake8-bugbear
[flake8-bugbear]
# 允许使用 assert 语句(默认 B011 警告 assert测试代码中常用故关闭
allow-asserts = True

@ -0,0 +1,125 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const router = express.Router();
const RULE_DIR = path.join(__dirname, '..', '..', 'rule');
if (!fs.existsSync(RULE_DIR)) {
fs.mkdirSync(RULE_DIR, { recursive: true });
}
const ruleStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, RULE_DIR);
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});
const ruleUpload = multer({
storage: ruleStorage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowed = [
'setup.cfg', '.pylintrc', '.flake8', 'pyproject.toml',
'pylintrc', 'flake8.cfg', 'tox.ini'
];
const extAllowed = /\.(cfg|ini|toml|yaml|yml|json)$/i;
if (allowed.includes(file.originalname) || extAllowed.test(file.originalname)) {
return cb(null, true);
}
return cb(new Error('不支持的规则文件类型'));
}
});
router.post('/rules/upload', ruleUpload.array('files'), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ success: false, error: '没有上传文件' });
}
const saved = req.files.map(f => f.originalname);
res.json({ success: true, data: { saved_files: saved, directory: RULE_DIR } });
} catch (error) {
console.error('规则文件上传失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.get('/rules', (req, res) => {
try {
if (!fs.existsSync(RULE_DIR)) {
return res.json({ success: true, data: [] });
}
const files = fs.readdirSync(RULE_DIR).map(name => {
const p = path.join(RULE_DIR, name);
const stat = fs.statSync(p);
return {
name,
size: stat.size,
modified_at: stat.mtime.toISOString(),
is_directory: stat.isDirectory()
};
}).filter(item => !item.is_directory);
res.json({ success: true, data: files });
} catch (error) {
console.error('读取规则列表失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.get('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const target = path.join(RULE_DIR, name);
if (!fs.existsSync(target)) {
return res.status(404).json({ success: false, error: '规则文件不存在' });
}
const content = fs.readFileSync(target, 'utf8');
res.json({ success: true, data: { name, content } });
} catch (error) {
console.error('读取规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.put('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const { content } = req.body || {};
if (!name) {
return res.status(400).json({ success: false, error: '规则文件名不能为空' });
}
const target = path.join(RULE_DIR, name);
const dir = path.dirname(target);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(target, content ?? '', 'utf8');
res.json({ success: true, message: '保存成功' });
} catch (error) {
console.error('保存规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.delete('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const target = path.join(RULE_DIR, name);
if (!fs.existsSync(target)) {
return res.status(404).json({ success: false, error: '规则文件不存在' });
}
fs.unlinkSync(target);
res.json({ success: true, message: '已删除' });
} catch (error) {
console.error('删除规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
module.exports = router;

File diff suppressed because it is too large Load Diff

@ -61,6 +61,26 @@ if %ERRORLEVEL% NEQ 0 (
)
echo.
echo ==========================================
echo 配置AI分析服务...
echo ==========================================
echo.
echo 设置科大讯飞API配置...
set AI_PROVIDER=xf
REM HTTP API使用APIpasswordBearer Token在控制台获取https://console.xfyun.cn/services/bmx1
REM 如果使用WebSocket协议则需要XF_API_KEY和XF_API_SECRET
set XF_API_PASSWORD=mfxzmIRlAymtEmsgVojM:ZZIvBMLpPhAHSeATcoDY
REM 兼容旧配置如果XF_API_PASSWORD未设置会尝试使用XF_API_KEY
set XF_API_KEY=mfxzmIRlAymtEmsgVojM
set XF_API_SECRET=ZZIvBMLpPhAHSeATcoDY
set XF_MODEL=spark-x
set XF_API_URL=https://spark-api-open.xf-yun.com/v2/chat/completions
set AI_ANALYZER_ENABLED=true
echo [提示] AI分析服务已配置为使用科大讯飞Spark X1.5 HTTP API
echo [提示] 请确保XF_API_PASSWORD已正确配置HTTP协议的APIpassword
echo [提示] 获取地址https://console.xfyun.cn/services/bmx1
echo.
echo ==========================================
echo 启动服务器...
echo ==========================================
@ -69,6 +89,8 @@ echo 服务器将在以下地址运行:
echo - 前端界面: http://localhost:5000
echo - API接口: http://localhost:5000/api
echo.
echo AI分析服务: 科大讯飞 Spark X1.5 API
echo.
echo 按 Ctrl+C 停止服务器
echo.

@ -1,50 +0,0 @@
"""
测试代码样例 - 用于测试 FortifyCode 系统
这个文件包含了各种常见的代码问题用于测试检查工具是否正常工作
包括代码风格问题安全问题代码质量问题等
"""
import os
import sys
# 缺少空行 (Flake8: E302)
def test_function():
"""测试函数"""
x=1+2 # 缺少空格 (Flake8: E225)
password = "hardcoded_password" # 硬编码密码 (Bandit: B105)
# SQL注入风险 (Bandit: B608)
query = "SELECT * FROM users WHERE id = " + str(x)
# 使用 eval 存在安全风险 (Bandit: B307)
result = eval("1 + 1")
# 行长度超过限制 (Flake8: E501)
very_long_variable_name = "这是一个非常非常非常非常非常非常非常非常非常非常长的字符串,用于测试行长度检查"
return result
# 缺少空行 (Flake8: E305)
class TestClass:
"""测试类"""
def __init__(self):
"""初始化方法"""
self.value = 42
def method(self, unused_param): # 未使用的参数 (Pylint: W0613)
"""测试方法"""
print(self.value) # 可以改用 logging
# 全局变量使用不当 (Pylint: C0103)
myVariable = "should_use_snake_case"
# 未使用的导入 (Pylint: W0611)
import json
# 主函数
if __name__ == "__main__":
test_function()
obj = TestClass()
obj.method("unused")

@ -1,80 +0,0 @@
const { exec } = require('child_process');
const path = require('path');
// 测试Flake8的实际输出
function testFlake8() {
const testFile = path.join(__dirname, 'test_sample.py');
console.log('测试文件路径:', testFile);
// 测试1: 默认格式
console.log('\n=== 测试1: flake8 默认格式 ===');
exec(`flake8 "${testFile}"`, (error, stdout, stderr) => {
console.log('默认格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
});
// 测试2: JSON格式
console.log('\n=== 测试2: flake8 --format=json ===');
exec(`flake8 --format=json "${testFile}"`, (error, stdout, stderr) => {
console.log('JSON格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
// 尝试解析JSON
try {
if (stdout.trim()) {
const parsed = JSON.parse(stdout);
console.log('JSON解析成功:', parsed);
} else {
console.log('输出为空');
}
} catch (e) {
console.log('JSON解析失败:', e.message);
console.log('原始输出长度:', stdout.length);
console.log('输出前50个字符:', stdout.substring(0, 50));
}
});
// 测试3: quiet格式不使用JSON
console.log('\n=== 测试3: flake8 --quiet ===');
exec(`flake8 --quiet "${testFile}"`, (error, stdout, stderr) => {
console.log('quiet格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
});
// 测试4: 使用临时文件路径(模拟实际使用场景)
const tempFile = 'C:\\Users\\张洋\\AppData\\Local\\Temp\\test_temp_file.py';
console.log('\n=== 测试4: 使用临时文件路径 ===');
exec(`flake8 --quiet "${testFile}"`, (error, stdout, stderr) => {
console.log('临时文件格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
// 模拟我们的解析逻辑
if (stdout.trim()) {
const lines = stdout.trim().split('\n').filter(line => line.trim());
console.log('解析后的行数:', lines.length);
for (const line of lines) {
console.log('处理行:', line);
const match = line.match(/^(.+?):(\d+):(\d+):\s*([A-Z]\d+)\s*(.+)$/);
if (match) {
console.log('解析成功:', {
file: match[1],
line: parseInt(match[2]),
column: parseInt(match[3]),
code: match[4],
message: match[5]
});
} else {
console.log('解析失败:', line);
}
}
}
});
}
testFlake8();

@ -1,31 +0,0 @@
const path = require('path');
const fs = require('fs');
// 模拟backend.js中的路径计算
function testPathCalculation() {
// 模拟__dirname的值 (view目录)
const mockDirname = path.join(__dirname, 'view');
console.log('当前目录:', __dirname);
console.log('模拟的__dirname:', mockDirname);
// 计算out目录路径
const outDir = path.join(mockDirname, '..', 'out');
console.log('计算出的out目录路径:', outDir);
// 规范化路径
const normalizedOutDir = path.resolve(outDir);
console.log('规范化后的out目录路径:', normalizedOutDir);
// 检查目录是否存在
if (fs.existsSync(normalizedOutDir)) {
console.log('目录存在');
// 列出目录内容
const files = fs.readdirSync(normalizedOutDir);
console.log('目录内容:', files);
} else {
console.log('目录不存在');
}
}
testPathCalculation();

@ -1,8 +0,0 @@
import os
def bad_function():
exec("print('Hello, world!')") # Potential security risk
os.system("echo This is a test")# Missing space after comment
if __name__=="__main__": # Missing spaces around ==
bad_function()

@ -1,42 +0,0 @@
# 代码质量检查API文档
## API端点
- **URL**`/check`
- **方法**POST
- **请求格式**multipart/form-data
- **请求参数**
- `file` (文件)要检查的Python代码文件必需
- `tools` (字符串):逗号分隔的工具列表,例如 "bandit,flake8,pylint"(必需,至少一个)。
- **响应格式**JSON
- 成功200 OK
```
{
"results": [
{
"tool": "bandit",
"stdout": "...检查输出...",
"stderr": "...错误信息(如果有)..."
},
// 其他工具的结果
]
}
```
- 错误400 Bad Request
```
{
"error": "No file uploaded" // 或其他错误消息
}
```
- **示例请求** (使用curl)
```
curl -X POST http://localhost:3000/check \
-F "file=@D:\软件工程\代码质量检查\src\test_sample.py" \
-F "tools=bandit,flake8,pylint"
```
- **注意**
- 支持的工具bandit、flake8、pylint大小写不敏感
- 文件会临时保存并在检查后删除。
- 如果工具运行失败stderr会包含错误详情。

@ -1,36 +0,0 @@
# 代码质量检查后端启动手册
## 前提条件
- Node.js 已安装(版本 >= 14
- Python 3.9 已安装且bandit、flake8、pylint 通过pip安装。
- 依赖express、multer、cors运行`npm install express multer cors`安装)。
## 启动步骤
1. 导航到项目目录:
```
cd D:\软件工程\代码质量检查\src
```
2. 安装Node.js依赖如果尚未
```
npm install express multer cors
```
3. 启动后端服务器:
```
node view/backend.js
```
- 控制台会显示“Server running on port 3000”。
- 服务器监听http://localhost:3000。
4. 测试API
- 使用curl示例或前端页面如果有`view/frontend.html`,通过浏览器打开并提交)。
- 示例:上传`test_sample.py`选择所有工具检查响应JSON。
## 停止服务器
按Ctrl+C在终端中停止。
## 常见问题
- 如果端口3000被占用修改`app.listen(3000)`为其他端口如5000
- CORS错误确保cors已启用如果从本地文件测试前端使用Live Server扩展避免origin null。
- 工具失败检查Python PATH工具必须在系统PATH中可用

@ -1,405 +0,0 @@
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
// 创建Express应用
const app = express();
const PORT = process.env.PORT || 3000;
// 配置CORS
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
preflightContinue: false,
optionsSuccessStatus: 204
}));
// 使用正则表达式处理所有OPTIONS请求
app.options(/.*/, (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.sendStatus(204);
});
// 解析JSON请求体
app.use(express.json());
// 请求日志中间件
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
// 配置Multer文件上传
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, os.tmpdir());
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
}
});
const upload = multer({
storage: storage,
limits: { fileSize: 10 * 1024 * 1024 }
});
// 工具配置
const TOOL_CONFIG = {
bandit: {
command: 'bandit',
args: (filePath) => `-r -f json -o - ${filePath}`,
parser: (stdout) => JSON.parse(stdout)
},
flake8: {
command: 'flake8',
args: (filePath) => `${filePath}`,
parser: (stdout) => {
console.log('Flake8原始输出:', stdout);
console.log('Flake8输出长度:', stdout.length);
console.log('Flake8输出是否为空:', !stdout || stdout.trim() === '');
// 处理空输出情况
if (!stdout || stdout.trim() === '') {
console.log('Flake8输出为空返回空数组');
return []; // 返回空数组
}
// 检查是否已经是JSON格式
if (stdout.trim().startsWith('[') || stdout.trim().startsWith('{')) {
try {
return JSON.parse(stdout);
} catch (parseError) {
console.log('Flake8 JSON解析失败:', parseError.message);
}
}
// 如果不是JSON格式尝试解析为文本格式
console.log('Flake8输出不是JSON格式使用文本解析');
const lines = stdout.trim().split('\n').filter(line => line.trim());
const issues = [];
for (const line of lines) {
// 跳过注释行和空行
if (line.startsWith('#') || !line.trim()) {
continue;
}
// 尝试解析flake8的标准输出格式
// 支持绝对路径和相对路径,以及不同的分隔符
const match = line.match(/^(.+?):(\d+):(\d+):\s*([A-Z]\d+)\s*(.+)$/);
if (match) {
issues.push({
file: match[1],
line: parseInt(match[2]),
column: parseInt(match[3]),
code: match[4],
message: match[5]
});
} else if (line.includes(':')) {
// 如果不匹配标准格式,但包含冒号,尝试更宽松的解析
const parts = line.split(':');
if (parts.length >= 4) {
const file = parts[0];
const lineNum = parseInt(parts[1]);
const colNum = parseInt(parts[2]);
const codeAndMessage = parts.slice(3).join(':').trim();
// 尝试提取错误代码
const codeMatch = codeAndMessage.match(/^([A-Z]\d+)\s*(.+)$/);
if (codeMatch) {
issues.push({
file: file,
line: lineNum,
column: colNum,
code: codeMatch[1],
message: codeMatch[2]
});
} else {
issues.push({
file: file,
line: lineNum,
column: colNum,
code: 'UNKNOWN',
message: codeAndMessage
});
}
} else {
console.log('无法解析的Flake8输出行:', line);
}
} else {
// 如果行不包含任何错误信息,可能是其他类型的输出
console.log('跳过Flake8输出行:', line);
}
}
console.log('Flake8文本解析结果:', issues);
return issues;
}
},
pylint: {
command: 'pylint',
args: (filePath) => `--output-format=json ${filePath}`,
parser: (stdout) => {
// 处理空输出情况
if (!stdout || stdout.trim() === '') {
return []; // 返回空数组
}
return JSON.parse(stdout);
}
}
};
// 确保out目录存在放在项目根目录避免触发开发服务器重载
function ensureOutDir() {
// 将out目录放在项目根目录而不是view目录内
const outDir = path.join(__dirname, '..', 'out');
console.log(`检查输出目录: ${outDir}`);
if (!fs.existsSync(outDir)) {
console.log(`创建输出目录: ${outDir}`);
fs.mkdirSync(outDir, { recursive: true });
console.log(`输出目录创建成功`);
} else {
console.log(`输出目录已存在: ${outDir}`);
}
return outDir;
}
// 运行单个工具
function runTool(tool, filePath) {
return new Promise((resolve, reject) => {
const config = TOOL_CONFIG[tool];
if (!config) {
return reject(new Error(`不支持的工具: ${tool}`));
}
let actualFilePath = filePath;
let actualCommand = `${config.command} ${config.args(filePath)}`;
// 对于Flake8添加额外的调试和临时文件处理
if (tool === 'flake8') {
console.log(`Flake8原始命令: ${actualCommand}`);
console.log(`文件路径存在性: ${fs.existsSync(filePath)}`);
// 先检查文件内容
try {
const content = fs.readFileSync(filePath, 'utf8');
console.log(`Flake8检查的文件内容:\n${content}`);
// 创建本地副本进行检查
const localFileName = `temp_check_${Date.now()}.py`;
const localFilePath = path.join(process.cwd(), localFileName);
fs.writeFileSync(localFilePath, content);
console.log(`创建本地文件副本: ${localFilePath}`);
// 修改命令使用本地文件
actualFilePath = localFilePath;
actualCommand = `${config.command} ${config.args(localFilePath)}`;
console.log(`修改后的Flake8命令: ${actualCommand}`);
} catch (e) {
console.error('Flake8文件处理失败:', e);
}
}
console.log(`执行命令: ${actualCommand}`);
exec(actualCommand, { shell: true }, (error, stdout, stderr) => {
console.log(`${tool}执行结果 - 退出码: ${error ? error.code : 0}`);
let result = {
stdout: stdout ? stdout.trim() : '',
stderr: stderr ? stderr.trim() : ''
};
console.log(`${tool} stdout长度: ${result.stdout.length}`);
console.log(`${tool} stderr长度: ${result.stderr.length}`);
if (tool === 'flake8') {
console.log(`Flake8 stdout内容: "${result.stdout}"`);
console.log(`Flake8 stderr内容: "${result.stderr}"`);
// 清理本地临时文件
if (actualFilePath !== filePath) {
setTimeout(() => {
try {
if (fs.existsSync(actualFilePath)) {
fs.unlinkSync(actualFilePath);
console.log(`清理Flake8本地临时文件: ${actualFilePath}`);
}
} catch (e) {
console.error('清理Flake8本地临时文件失败:', e);
}
}, 1000);
}
}
try {
if (result.stdout) {
result.parsed = config.parser(result.stdout);
}
} catch (parseError) {
console.warn(`解析${tool}输出失败:`, parseError);
// 解析失败时返回原始输出
result.parsed = {
error: `解析失败: ${parseError.message}`,
rawOutput: result.stdout
};
}
resolve(result);
});
});
}
// 生成报告
function generateReport(results, filename) {
let report = `# 代码质量检查报告\n\n`;
report += `**文件:** ${filename}\n`;
report += `**检查时间:** ${new Date().toLocaleString()}\n\n`;
results.forEach(toolResult => {
report += `## ${toolResult.tool} 检查结果\n`;
if (toolResult.stdout) {
report += '### 输出:\n```\n';
report += toolResult.stdout;
report += '\n```\n\n';
}
if (toolResult.stderr) {
report += '### 错误:\n```\n';
report += toolResult.stderr;
report += '\n```\n\n';
}
if (toolResult.parsed) {
report += '### 解析结果:\n```json\n';
report += JSON.stringify(toolResult.parsed, null, 2);
report += '\n```\n\n';
}
});
return report;
}
// 健康检查端点
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
timestamp: new Date().toISOString()
});
});
// 检查端点
app.post('/check', upload.single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: '未上传文件' });
}
const tools = req.body.tools ? req.body.tools.split(',') : ['bandit', 'flake8', 'pylint'];
const filePath = req.file.path;
const results = [];
// 调试:检查临时文件内容
console.log(`临时文件路径: ${filePath}`);
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
console.log(`临时文件内容 (${fileContent.length} 字符):\n${fileContent}`);
} catch (error) {
console.error('读取临时文件失败:', error);
}
for (const tool of tools) {
console.log(`开始执行工具: ${tool}`);
const result = await runTool(tool, filePath);
console.log(`${tool} 执行结果 - stdout长度: ${result.stdout.length}, stderr长度: ${result.stderr.length}`);
results.push({
tool: tool,
stdout: result.stdout,
stderr: result.stderr,
parsed: result.parsed
});
}
const outDir = ensureOutDir();
const reportContent = generateReport(results, req.file.originalname);
const reportFilename = `report_${Date.now()}.md`;
const reportPath = path.join(outDir, reportFilename);
console.log(`正在生成报告文件: ${reportPath}`);
console.log(`报告内容长度: ${reportContent.length} 字符`);
fs.writeFileSync(reportPath, reportContent);
console.log(`报告文件生成成功: ${reportPath}`);
res.json({
success: true,
results: results,
reportUrl: `/reports/${reportFilename}`
});
} catch (error) {
console.error('检查错误:', error);
res.status(500).json({
success: false,
error: error.message
});
} finally {
if (req.file && fs.existsSync(req.file.path)) {
fs.unlink(req.file.path, (err) => {
if (err) console.error('删除临时文件失败:', err);
});
}
}
});
// 报告下载端点
app.get('/reports/:filename', (req, res) => {
const filename = req.params.filename;
// 从项目根目录的out文件夹读取文件
const filePath = path.join(__dirname, '..', 'out', filename);
if (fs.existsSync(filePath)) {
res.setHeader('Content-Type', 'text/markdown');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
setTimeout(() => {
try {
fs.unlinkSync(filePath);
console.log(`已删除报告文件: ${filename}`);
} catch (err) {
console.error(`删除报告文件失败: ${err.message}`);
}
}, 5000);
} else {
res.status(404).json({
error: '报告未找到',
filename: filename
});
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
console.log(`上传文件临时目录: ${os.tmpdir()}`);
console.log(`报告输出目录: ${path.join(__dirname, '..', 'out')}`);
});
module.exports = app;

@ -1,575 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代码质量检查工具</title>
<!-- 添加元标签防止缓存和刷新 -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<!-- 防止自动刷新 -->
<meta http-equiv="refresh" content="999999">
<style>
/* 保持之前的样式不变 */
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
}
.tool-checkbox {
margin: 15px 0;
padding: 10px;
background-color: #f1f8ff;
border-radius: 4px;
}
.file-upload {
margin: 20px 0;
}
.download-btn {
display: inline-block;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
transition: background-color 0.3s;
margin-top: 15px;
}
.download-btn:hover {
background-color: #45a049;
}
#report-area {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
display: none;
background-color: #e8f5e9;
}
#result {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
max-height: 400px;
overflow: auto;
white-space: pre-wrap;
font-family: monospace;
margin-top: 20px;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 20px auto;
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #2980b9;
}
.btn:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>代码质量检查工具</h1>
<div id="toolContainer">
<fieldset>
<legend>选择工具:</legend>
<div class="tool-checkbox">
<input type="checkbox" id="bandit" name="tools" value="bandit" checked>
<label for="bandit">Bandit</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="flake8" name="tools" value="flake8" checked>
<label for="flake8">Flake8</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="pylint" name="tools" value="pylint" checked>
<label for="pylint">Pylint</label>
</div>
</fieldset>
<div class="file-upload">
<label for="file">上传代码文件:</label>
<input type="file" id="file" name="file" accept=".py" aria-label="选择Python文件">
</div>
<button type="button" id="submitBtn" class="btn">提交检查</button>
<!-- 调试按钮 -->
<div style="margin-top: 20px; padding: 10px; background-color: #f0f0f0; border-radius: 4px;">
<button type="button" id="toggleRefreshBtn" class="btn"
style="background-color: #e74c3c;">启用防刷新</button>
<span id="refreshStatus" style="margin-left: 10px;">防刷新: 禁用</span>
</div>
</div>
<div class="loader" id="loader"></div>
<div id="report-area" aria-live="polite">
<h2>检查报告</h2>
<p>报告已生成,点击下载:</p>
<a id="report-download" href="#" class="download-btn">下载报告</a>
<button id="close-report" class="btn" style="margin-left: 10px;">关闭</button>
</div>
<h2>结果:</h2>
<pre id="result" role="log" aria-live="polite"></pre>
</div>
<script>
// 创建独立的事件处理模块
const App = (() => {
// DOM元素引用
const elements = {
submitBtn: document.getElementById('submitBtn'),
resultPre: document.getElementById('result'),
reportArea: document.getElementById('report-area'),
reportLink: document.getElementById('report-download'),
closeReportBtn: document.getElementById('close-report'),
loader: document.getElementById('loader'),
fileInput: document.getElementById('file'),
banditCheckbox: document.getElementById('bandit'),
flake8Checkbox: document.getElementById('flake8'),
pylintCheckbox: document.getElementById('pylint'),
toggleRefreshBtn: document.getElementById('toggleRefreshBtn'),
refreshStatus: document.getElementById('refreshStatus')
};
// 状态管理
let isProcessing = false;
let preventRefresh = false; // 防刷新状态
// 设置防刷新拦截器
const setupRefreshPrevention = () => {
// 拦截所有点击事件
document.addEventListener('click', function (event) {
if (preventRefresh) {
console.log('拦截到点击事件:', event.target.tagName, event.target.id || event.target.textContent);
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截所有表单提交
document.addEventListener('submit', function (event) {
console.log('拦截到表单提交事件');
event.preventDefault();
event.stopPropagation();
return false;
}, true);
// 拦截键盘事件F5等
document.addEventListener('keydown', function (event) {
if (preventRefresh && (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.ctrlKey && event.key === 'R'))) {
console.log('拦截到刷新快捷键:', event.key);
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截所有链接点击
document.addEventListener('click', function (event) {
if (preventRefresh && event.target.tagName === 'A') {
console.log('拦截到链接点击:', event.target.href);
// 只有非下载链接才阻止
if (!event.target.href.includes('reports/')) {
event.preventDefault();
event.stopPropagation();
return false;
}
}
}, true);
// 添加页面刷新监听器
window.addEventListener('beforeunload', function (event) {
console.log('页面即将被刷新或关闭');
if (preventRefresh) {
event.preventDefault();
event.returnValue = '';
return '';
}
});
};
// 初始化应用
const init = () => {
// 初始隐藏报告区域
elements.reportArea.style.display = 'none';
// 设置防刷新拦截器
setupRefreshPrevention();
// 绑定事件监听器
bindEventListeners();
};
// 绑定所有事件监听器
const bindEventListeners = () => {
// 关闭报告区域
elements.closeReportBtn.addEventListener('click', handleCloseReport);
// 提交按钮点击事件
elements.submitBtn.addEventListener('click', handleSubmit);
// 下载链接点击事件
elements.reportLink.addEventListener('click', handleDownload);
// 切换防刷新状态
elements.toggleRefreshBtn.addEventListener('click', handleToggleRefresh);
};
// 处理关闭报告
const handleCloseReport = (event) => {
event.preventDefault();
elements.reportArea.style.display = 'none';
};
// 处理切换防刷新状态
const handleToggleRefresh = (event) => {
event.preventDefault();
preventRefresh = !preventRefresh;
if (preventRefresh) {
elements.toggleRefreshBtn.textContent = '禁用防刷新';
elements.toggleRefreshBtn.style.backgroundColor = '#27ae60';
elements.refreshStatus.textContent = '防刷新: 启用';
console.log('防刷新已启用');
} else {
elements.toggleRefreshBtn.textContent = '启用防刷新';
elements.toggleRefreshBtn.style.backgroundColor = '#e74c3c';
elements.refreshStatus.textContent = '防刷新: 禁用';
console.log('防刷新已禁用');
}
};
// 处理提交
const handleSubmit = async (event) => {
console.log('提交按钮被点击');
// 1. 阻止所有默认行为
event.preventDefault();
event.stopPropagation();
console.log('阻止了默认行为和冒泡');
// 2. 防止重复提交
if (isProcessing) {
console.log('正在处理中,忽略重复点击');
return;
}
isProcessing = true;
// 3. 启用全局防刷新拦截器
preventRefresh = true;
console.log('启用全局防刷新拦截器');
console.log('开始处理请求');
// 3. 更新UI状态
updateUIState(true);
try {
// 4. 验证输入
console.log('验证输入...');
const validationError = validateInputs();
if (validationError) {
console.log('输入验证失败:', validationError);
elements.resultPre.textContent = validationError;
return;
}
console.log('输入验证通过');
// 5. 准备数据
console.log('准备表单数据...');
const formData = prepareFormData();
// 6. 发送请求
console.log('发送请求到后端...');
const data = await sendRequest(formData);
console.log('请求成功完成');
// 7. 处理响应
console.log('处理响应数据...');
processResponse(data);
console.log('响应处理完成');
} catch (error) {
// 8. 错误处理
console.log('处理过程中发生错误:', error);
handleError(error);
} finally {
// 9. 恢复UI状态
console.log('恢复UI状态');
updateUIState(false);
isProcessing = false;
// 10. 禁用全局防刷新拦截器
preventRefresh = false;
console.log('禁用全局防刷新拦截器');
}
};
// 更新UI状态
const updateUIState = (isLoading) => {
if (isLoading) {
elements.submitBtn.disabled = true;
elements.submitBtn.textContent = '检查中...';
elements.loader.style.display = 'block';
elements.resultPre.textContent = '正在检查...';
elements.reportArea.style.display = 'none';
} else {
elements.submitBtn.disabled = false;
elements.submitBtn.textContent = '提交检查';
elements.loader.style.display = 'none';
}
};
// 验证输入
const validateInputs = () => {
if (elements.fileInput.files.length === 0) {
return '请上传一个文件。';
}
const tools = getSelectedTools();
if (tools.length === 0) {
return '请至少选择一个工具。';
}
return null;
};
// 获取选中的工具
const getSelectedTools = () => {
const tools = [];
if (elements.banditCheckbox.checked) tools.push('bandit');
if (elements.flake8Checkbox.checked) tools.push('flake8');
if (elements.pylintCheckbox.checked) tools.push('pylint');
return tools;
};
// 准备表单数据
const prepareFormData = () => {
const formData = new FormData();
formData.append('file', elements.fileInput.files[0]);
formData.append('tools', getSelectedTools().join(','));
return formData;
};
// 发送请求
const sendRequest = async (formData) => {
console.log('开始发送fetch请求...');
console.log('请求URL: http://localhost:3000/check');
try {
const response = await fetch('http://localhost:3000/check', {
method: 'POST',
body: formData
});
console.log('收到响应,状态码:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('HTTP错误详情:', {
status: response.status,
statusText: response.statusText,
errorText: errorText
});
throw new Error(`HTTP错误! 状态码: ${response.status}\n${errorText}`);
}
const data = await response.json();
console.log('成功解析JSON响应:', data);
return data;
} catch (error) {
console.error('网络请求异常:', error);
// 重新抛出错误,让上层处理
throw error;
}
};
// 处理响应
const processResponse = (data) => {
// 显示结果
elements.resultPre.textContent = JSON.stringify(data, null, 2);
// 显示报告下载区域
if (data.reportUrl) {
const downloadUrl = 'http://localhost:3000' + data.reportUrl;
elements.reportLink.href = downloadUrl;
elements.reportLink.download = `code_report_${Date.now()}.md`;
elements.reportArea.style.display = 'block';
}
};
// 处理错误
const handleError = (error) => {
elements.resultPre.textContent = `错误: ${error.message}`;
console.error('请求失败:', error);
};
// 处理下载
const handleDownload = async (event) => {
event.preventDefault();
event.stopPropagation();
console.log('开始下载报告...');
try {
// 使用fetch API下载文件
const response = await fetch(elements.reportLink.href);
if (!response.ok) {
throw new Error(`下载失败: ${response.status}`);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
// 创建临时下载链接
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `code_report_${Date.now()}.md`;
document.body.appendChild(a);
a.click();
// 清理临时资源
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
console.log('下载成功');
} catch (error) {
console.error('下载失败:', error);
alert('下载失败: ' + error.message);
}
};
// 公开初始化方法和状态
return {
init,
get isProcessing() { return isProcessing; }
};
})();
// 防刷新拦截器将在App模块内部设置
// 添加页面刷新监听器用于调试将在App模块中设置
// 监听页面加载完成
window.addEventListener('load', function () {
console.log('页面加载完成');
});
// 监听页面显示状态变化
document.addEventListener('visibilitychange', function () {
console.log('页面可见性变化:', document.visibilityState);
});
// 全局错误处理,防止页面重新加载
window.addEventListener('error', function (event) {
console.error('捕获到全局错误:', event.error);
console.error('错误信息:', event.message);
console.error('错误文件:', event.filename);
console.error('错误行号:', event.lineno);
console.error('错误列号:', event.colno);
// 阻止默认的错误处理行为(可能导致页面重新加载)
event.preventDefault();
return false;
});
// 捕获未处理的Promise错误
window.addEventListener('unhandledrejection', function (event) {
console.error('捕获到未处理的Promise错误:', event.reason);
console.error('Promise错误详情:', event);
// 阻止默认行为
event.preventDefault();
});
// 防止意外的页面刷新
window.addEventListener('beforeunload', function (event) {
// 如果正在处理请求,阻止页面刷新
if (typeof App !== 'undefined' && App.isProcessing) {
console.log('阻止页面刷新,因为正在处理请求');
event.preventDefault();
event.returnValue = '正在处理请求,请稍等...';
return '正在处理请求,请稍等...';
}
});
// 初始化应用
document.addEventListener('DOMContentLoaded', function () {
console.log('DOM内容加载完成开始初始化应用');
try {
App.init();
console.log('应用初始化成功');
} catch (error) {
console.error('应用初始化失败:', error);
// 即使初始化失败,也不要重新加载页面
alert('应用初始化失败,请刷新页面重试');
}
});
</script>
</body>
</html>

@ -1,347 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简化的代码质量检查工具</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
}
.tool-checkbox {
margin: 15px 0;
padding: 10px;
background-color: #f1f8ff;
border-radius: 4px;
}
.file-upload {
margin: 20px 0;
}
.download-btn {
display: inline-block;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
transition: background-color 0.3s;
margin-top: 15px;
}
.download-btn:hover {
background-color: #45a049;
}
#report-area {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
display: none;
background-color: #e8f5e9;
}
#result {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
max-height: 400px;
overflow: auto;
white-space: pre-wrap;
font-family: monospace;
margin-top: 20px;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 20px auto;
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #2980b9;
}
.btn:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.debug-panel {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin-top: 20px;
font-family: monospace;
font-size: 12px;
max-height: 200px;
overflow: auto;
}
</style>
</head>
<body>
<div class="container">
<h1>简化的代码质量检查工具</h1>
<div id="toolContainer">
<fieldset>
<legend>选择工具:</legend>
<div class="tool-checkbox">
<input type="checkbox" id="bandit" name="tools" value="bandit" checked>
<label for="bandit">Bandit</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="flake8" name="tools" value="flake8" checked>
<label for="flake8">Flake8</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="pylint" name="tools" value="pylint" checked>
<label for="pylint">Pylint</label>
</div>
</fieldset>
<div class="file-upload">
<label for="file">上传代码文件:</label>
<input type="file" id="file" name="file" accept=".py" aria-label="选择Python文件">
</div>
<button type="button" id="submitBtn" class="btn">提交检查</button>
</div>
<div class="loader" id="loader"></div>
<div id="report-area" aria-live="polite">
<h2>检查报告</h2>
<p>报告已生成,点击下载:</p>
<a id="report-download" href="#" class="download-btn">下载报告</a>
<button id="close-report" class="btn" style="margin-left: 10px;">关闭</button>
</div>
<h2>结果:</h2>
<pre id="result" role="log" aria-live="polite"></pre>
<div class="debug-panel">
<strong>调试信息:</strong><br>
<div id="debug-log"></div>
</div>
</div>
<script>
// 简单的调试日志函数
function debugLog(message, data = null) {
const timestamp = new Date().toLocaleTimeString();
const logEntry = `[${timestamp}] ${message}`;
if (data) {
console.log(logEntry, data);
} else {
console.log(logEntry);
}
const debugDiv = document.getElementById('debug-log');
debugDiv.innerHTML += logEntry + '<br>';
debugDiv.scrollTop = debugDiv.scrollHeight;
}
// 页面加载
document.addEventListener('DOMContentLoaded', function () {
debugLog('页面DOM加载完成');
const submitBtn = document.getElementById('submitBtn');
const resultPre = document.getElementById('result');
const reportArea = document.getElementById('report-area');
const reportLink = document.getElementById('report-download');
const closeReportBtn = document.getElementById('close-report');
const loader = document.getElementById('loader');
const fileInput = document.getElementById('file');
let isProcessing = false;
// 初始隐藏报告区域
reportArea.style.display = 'none';
// 关闭报告区域
closeReportBtn.addEventListener('click', function (event) {
event.preventDefault();
reportArea.style.display = 'none';
debugLog('报告区域已关闭');
});
// 提交按钮处理
submitBtn.addEventListener('click', async function (event) {
debugLog('提交按钮被点击');
// 阻止所有默认行为
event.preventDefault();
event.stopPropagation();
debugLog('阻止了默认行为和冒泡');
// 防止重复提交
if (isProcessing) {
debugLog('正在处理中,忽略重复点击');
return;
}
isProcessing = true;
debugLog('开始处理请求');
// 更新UI状态
submitBtn.disabled = true;
submitBtn.textContent = '检查中...';
loader.style.display = 'block';
resultPre.textContent = '正在检查...';
reportArea.style.display = 'none';
try {
// 验证文件
if (fileInput.files.length === 0) {
throw new Error('请上传一个文件');
}
debugLog('文件验证通过');
// 获取选中的工具
const tools = [];
if (document.getElementById('bandit').checked) tools.push('bandit');
if (document.getElementById('flake8').checked) tools.push('flake8');
if (document.getElementById('pylint').checked) tools.push('pylint');
if (tools.length === 0) {
throw new Error('请至少选择一个工具');
}
debugLog('工具选择:', tools);
// 准备表单数据
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('tools', tools.join(','));
debugLog('表单数据准备完成');
// 发送请求
debugLog('开始发送网络请求...');
const response = await fetch('http://localhost:3000/check', {
method: 'POST',
body: formData
});
debugLog('收到响应,状态码:', response.status);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP错误! 状态码: ${response.status}\n${errorText}`);
}
const data = await response.json();
debugLog('成功解析响应数据');
debugLog('响应数据结构:', data);
// 显示结果
resultPre.textContent = JSON.stringify(data, null, 2);
debugLog('结果已显示在页面上');
// 显示报告下载区域
if (data.reportUrl) {
const downloadUrl = 'http://localhost:3000' + data.reportUrl;
reportLink.href = downloadUrl;
reportLink.download = `code_report_${Date.now()}.md`;
reportArea.style.display = 'block';
debugLog('报告下载链接已设置');
}
} catch (error) {
debugLog('处理过程中发生错误:', error.message);
resultPre.textContent = `错误: ${error.message}`;
console.error('详细错误信息:', error);
} finally {
// 恢复UI状态
submitBtn.disabled = false;
submitBtn.textContent = '提交检查';
loader.style.display = 'none';
isProcessing = false;
debugLog('UI状态已恢复');
}
});
// 下载链接处理
reportLink.addEventListener('click', function (event) {
event.preventDefault();
debugLog('下载链接被点击');
// 使用简单的下载方式
const link = document.createElement('a');
link.href = reportLink.href;
link.download = reportLink.download;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
debugLog('下载已启动');
});
debugLog('事件监听器设置完成');
});
// 全局错误处理
window.addEventListener('error', function (event) {
console.error('全局JavaScript错误:', event.error);
debugLog('捕获到全局错误: ' + event.message);
});
window.addEventListener('unhandledrejection', function (event) {
console.error('未处理的Promise错误:', event.reason);
debugLog('捕获到未处理Promise错误: ' + event.reason);
});
debugLog('脚本初始化完成');
</script>
</body>
</html>

@ -1,193 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防刷新测试页面</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.test-section {
margin: 20px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
.btn:hover {
background-color: #2980b9;
}
.status {
margin: 10px 0;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>防刷新测试页面</h1>
<div class="test-section">
<h2>防刷新状态控制</h2>
<button id="toggleRefresh" class="btn">启用防刷新</button>
<span id="refreshStatus">防刷新: 禁用</span>
</div>
<div class="test-section">
<h2>测试按钮</h2>
<button id="testBtn1" class="btn">测试按钮1</button>
<button id="testBtn2" class="btn">测试按钮2</button>
<button id="testBtn3" class="btn">测试按钮3</button>
<a id="testLink" href="#" class="btn" style="text-decoration: none;">测试链接</a>
</div>
<div class="test-section">
<h2>结果显示</h2>
<div id="results" class="status">
点击按钮查看结果...
</div>
</div>
<div class="test-section">
<h2>键盘测试</h2>
<p>尝试按 F5 或 Ctrl+R 键测试防刷新功能</p>
<div id="keyboardResults" class="status">
键盘事件将在这里显示...
</div>
</div>
<script>
// 防刷新状态
let preventRefresh = false;
let eventCount = 0;
// DOM元素
const elements = {
toggleRefresh: document.getElementById('toggleRefresh'),
refreshStatus: document.getElementById('refreshStatus'),
testBtn1: document.getElementById('testBtn1'),
testBtn2: document.getElementById('testBtn2'),
testBtn3: document.getElementById('testBtn3'),
testLink: document.getElementById('testLink'),
results: document.getElementById('results'),
keyboardResults: document.getElementById('keyboardResults')
};
// 全局防刷新拦截器
function setupRefreshPrevention() {
// 拦截所有点击事件
document.addEventListener('click', function (event) {
if (preventRefresh) {
eventCount++;
console.log(`拦截到点击事件 #${eventCount}:`, event.target.tagName, event.target.id || event.target.textContent);
elements.results.innerHTML += `<br>点击事件 #${eventCount}: ${event.target.tagName} - ${event.target.id || event.target.textContent}`;
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截键盘事件
document.addEventListener('keydown', function (event) {
if (preventRefresh) {
console.log('拦截到键盘事件:', event.key, event.ctrlKey ? 'Ctrl+' : '');
elements.keyboardResults.innerHTML += `<br>键盘事件: ${event.key}${event.ctrlKey ? ' (Ctrl)' : ''}`;
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截链接点击
document.addEventListener('click', function (event) {
if (preventRefresh && event.target.tagName === 'A') {
console.log('拦截到链接点击:', event.target.href);
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
}
// 切换防刷新状态
elements.toggleRefresh.addEventListener('click', function (event) {
event.preventDefault();
preventRefresh = !preventRefresh;
if (preventRefresh) {
elements.toggleRefresh.textContent = '禁用防刷新';
elements.toggleRefresh.style.backgroundColor = '#27ae60';
elements.refreshStatus.textContent = '防刷新: 启用';
console.log('防刷新已启用');
} else {
elements.toggleRefresh.textContent = '启用防刷新';
elements.toggleRefresh.style.backgroundColor = '#3498db';
elements.refreshStatus.textContent = '防刷新: 禁用';
eventCount = 0;
elements.results.innerHTML = '点击按钮查看结果...';
elements.keyboardResults.innerHTML = '键盘事件将在这里显示...';
console.log('防刷新已禁用');
}
});
// 添加测试按钮事件
[elements.testBtn1, elements.testBtn2, elements.testBtn3].forEach((btn, index) => {
btn.addEventListener('click', function (event) {
if (!preventRefresh) {
elements.results.innerHTML += `<br>按钮 ${index + 1} 被点击`;
}
});
});
// 添加测试链接事件
elements.testLink.addEventListener('click', function (event) {
if (!preventRefresh) {
elements.results.innerHTML += '<br>链接被点击';
}
});
// 页面事件监听器
window.addEventListener('beforeunload', function (event) {
console.log('页面即将被刷新或关闭');
if (preventRefresh) {
event.preventDefault();
event.returnValue = '';
return '';
}
});
// 初始化
setupRefreshPrevention();
console.log('防刷新测试页面初始化完成');
// 添加页面加载时间戳
const loadTime = new Date().toLocaleString();
console.log('页面加载时间:', loadTime);
document.body.insertAdjacentHTML('afterbegin',
`<div style="background: #e8f5e9; padding: 10px; margin-bottom: 20px; border-radius: 4px;">
页面加载时间: ${loadTime}
</div>`
);
</script>
</body>
</html>

@ -0,0 +1,169 @@
# 系统创新点与差异化优势说明
## 一、与华为CodeArts的差异化分析
### 华为CodeArts的主要特点
- 企业级DevOps平台集成代码检查、CI/CD、项目管理等
- 主要面向云端部署,依赖华为云服务
- 商业产品,需要付费使用
- 功能全面但相对封闭,定制化程度有限
### 本系统的核心创新点
#### 1. **本地AI模型支持与数据安全创新** ⭐⭐⭐
**创新性**:这是本系统最重要的创新点
- **本地AI模型集成**支持Ollama等本地AI服务实现完全离线的代码质量分析
- **数据安全保护**:敏感代码(如军工、金融)无需上传到云端,数据完全本地处理
- **云端/本地无缝切换**通过统一接口实现本地AI和云端AI科大讯飞Spark、OpenAI的灵活切换
- **多AI服务适配**支持多种AI服务提供商不绑定单一厂商
**与CodeArts的差异**
- CodeArts主要依赖云端AI服务数据需要上传到华为云
- 本系统提供本地AI选项满足数据不出域的安全要求
- 特别适合军工、金融等对数据安全要求极高的场景
#### 2. **多工具结果智能去重算法创新** ⭐⭐
**创新性**:算法层面的创新
- **多层次去重机制**
- 精确匹配基于文件路径、行号、规则ID
- 模糊匹配基于Jaccard相似度算法识别语义相似的问题
- 跨工具去重自动识别Pylint、Flake8、Bandit检测到的重复问题
- **智能合并策略**:不仅识别重复,还能智能合并相似问题的分析结果
**与CodeArts的差异**
- CodeArts可能只是简单的结果展示缺乏深度的智能去重
- 本系统的Jaccard相似度算法能够识别语义相似但表述不同的问题
- 减少开发者需要处理的重复问题,提高效率
#### 3. **批量AI分析与上下文感知创新** ⭐⭐
**创新性**AI应用方式的创新
- **批量处理优化**一次性将所有问题发送给AI而不是逐个处理大幅提升分析速度
- **完整代码上下文**AI分析时提供完整文件内容而非仅代码片段提供更准确的上下文感知分析
- **统一分析策略**AI先合并重复问题再统一分析避免重复建议
**与CodeArts的差异**
- 传统方式逐个问题分析,效率低且缺乏全局视角
- 本系统批量分析+完整上下文,提供更准确、更一致的建议
- 减少AI调用次数降低成本
#### 4. **军工代码合规性定制化创新** ⭐
**创新性**:应用场景的创新
- **规则集灵活配置**:支持.pylintrc、setup.cfg、.flake8等多种配置文件格式
- **行业定制化**:特别针对军工领域对代码质量的特殊要求,提供定制化的代码合规性检查
- **规则文件管理**:支持规则文件的上传、编辑、应用和管理
**与CodeArts的差异**
- CodeArts是通用平台难以满足特定行业的特殊合规要求
- 本系统提供灵活的规则定制能力,适应不同行业标准
- 特别适合有严格合规要求的场景
#### 5. **轻量级Web平台与开源特性** ⭐
**创新性**:架构和部署的创新
- **轻量级架构**前后端分离Node.js+Express部署简单
- **开源可定制**:代码开源,可根据需求自由定制和扩展
- **本地部署**:支持完全本地部署,不依赖云服务
**与CodeArts的差异**
- CodeArts是商业产品需要付费定制化受限
- 本系统开源免费,可自由修改和扩展
- 适合中小团队、教育机构、个人开发者使用
---
## 二、回答策略建议
### 回答模板1强调数据安全和本地AI创新
**老师您提到的华为CodeArts确实是一个优秀的企业级平台。但我们的系统在以下几个方面有独特的创新价值**
**首先最重要的是本地AI模型支持。** CodeArts主要依赖云端AI服务数据需要上传到华为云。而我们的系统支持Ollama等本地AI模型可以实现完全离线的代码质量分析。这对于军工、金融等对数据安全要求极高的场景非常重要因为敏感代码可以完全在本地处理数据不出域。
**其次我们实现了云端和本地AI的无缝切换。** 通过统一接口用户可以根据需求选择使用本地AI保证数据安全或云端AI获得更强的分析能力这种灵活性是CodeArts所不具备的。
**第三,我们在多工具结果去重方面有算法创新。** 我们不仅做简单的精确匹配去重还使用Jaccard相似度算法进行模糊匹配能够识别语义相似但表述不同的问题这是传统工具所缺乏的。
**最后,我们的系统是开源轻量级的。** CodeArts是商业产品需要付费且定制化受限。我们的系统开源免费特别适合中小团队、教育机构使用也更容易根据特定行业需求进行定制化开发。
**因此我们的系统不是CodeArts的简单复制而是在数据安全、本地AI支持、智能去重算法等方面有独特创新面向的是不同的应用场景和用户群体。**
---
### 回答模板2强调算法创新和应用场景创新
**老师,我理解您的关注。让我从创新角度来说明我们系统的独特价值:**
**1. 算法层面的创新:**
- **智能去重算法**我们使用Jaccard相似度算法进行模糊匹配能够识别不同工具检测到的语义相似问题这是传统工具简单去重所不具备的
- **批量AI分析策略**:我们不是逐个问题分析,而是批量处理+完整代码上下文让AI有全局视角提供更准确、更一致的建议
**2. 应用场景的创新:**
- **本地AI支持**这是CodeArts等商业平台所不具备的。我们支持Ollama等本地AI满足数据不出域的安全要求特别适合军工、金融等敏感领域
- **行业定制化**我们提供灵活的规则集管理可以针对军工等特定行业的合规要求进行定制而CodeArts作为通用平台难以满足这些特殊需求
**3. 技术架构的创新:**
- **多AI服务适配**通过统一接口支持科大讯飞Spark、OpenAI、本地Ollama等多种AI服务不绑定单一厂商
- **轻量级开源架构**相比CodeArts的商业化、重量级平台我们的系统更轻量、更灵活适合中小团队和教育场景
**因此,我们的创新点不在于"有没有AI",而在于"如何更好地应用AI"和"如何满足特定场景需求"。我们的系统在数据安全、算法优化、场景定制等方面有独特价值。**
---
### 回答模板3从研究价值角度回答
**老师,从学术研究的角度,我们的系统在以下方面有研究价值:**
**1. 多工具协同的智能去重算法研究:**
- 我们提出了基于Jaccard相似度的跨工具问题去重算法这在学术上是有研究价值的
- 如何准确识别不同工具检测到的语义相似问题,是一个值得研究的算法问题
**2. 本地AI与云端AI的统一接口设计**
- 我们设计了一个统一接口能够无缝切换本地AI和云端AI这在架构设计上有创新
- 如何平衡数据安全和AI能力是一个实际应用中的重要问题
**3. 批量AI分析与上下文感知的优化**
- 我们研究了如何通过批量处理和完整上下文来提升AI分析的准确性和效率
- 这是一个AI应用优化的研究问题
**4. 特定领域的代码合规性检查:**
- 我们针对军工等特定领域进行了定制化研究,这是应用层面的创新
- 如何将通用工具适配到特定行业需求,是一个有价值的研究方向
**因此虽然CodeArts在商业应用上很成熟但我们的系统在算法研究、架构设计、应用创新等方面有独特的学术价值。**
---
## 三、关键数据支撑(如果可能,准备一些数据)
1. **去重效果**我们的智能去重算法能够减少X%的重复问题
2. **分析效率**批量AI分析比逐个分析快X倍
3. **数据安全**本地AI模式确保100%的数据不出域
4. **成本对比**:开源免费 vs CodeArts的商业费用
---
## 四、总结
**核心创新点排序(按重要性):**
1. **本地AI模型支持** - 数据安全创新,满足敏感场景需求
2. **智能去重算法** - 算法创新,提升分析效率
3. **批量AI分析** - AI应用优化提升准确性和效率
4. **行业定制化** - 应用场景创新,满足特定需求
5. **开源轻量架构** - 架构创新,降低使用门槛
**与CodeArts的定位差异**
- CodeArts企业级、商业化、云端为主、通用平台
- 本系统开源、轻量级、本地AI支持、行业定制、教育友好
**回答要点:**
- 承认CodeArts的优秀但强调我们的差异化创新
- 重点突出本地AI支持数据安全
- 强调算法创新(智能去重)
- 说明应用场景的不同(军工、教育等)
- 从研究价值角度说明学术贡献

@ -0,0 +1,83 @@
# 项目清理说明
## 已删除的文件
### 1. 测试文件(根目录)
- ✅ `test_code_sample.py` - 测试代码样例
- ✅ `test_flake8.js` - Flake8测试脚本
- ✅ `test_path.js` - 路径测试脚本
- ✅ `test_sample.py` - 简单测试样例
### 2. 设计文档
- ✅ `配置中心设计.md` - 配置中心设计文档(功能已实现)
- ✅ `项目集成说明.md` - 项目集成说明文档(功能已实现)
## 保留的文档
### 核心项目文档
- ✅ `README.md` - 项目说明文档
- ✅ `项目功能说明.md` - 项目功能说明
- ✅ `研究背景.md` - 研究背景
- ✅ `快速开始指南.md` - 快速开始指南
- ✅ `创新点说明.md` - 创新点说明(用于答辩)
## 建议进一步清理的内容
### 1. 第三方工具的文档和测试(可选)
以下目录包含第三方工具pylint、flake8、bandit的源码、文档和测试如果不需要可以删除
- `pylint-main/doc/` - Pylint文档目录
- `pylint-main/tests/` - Pylint测试目录
- `flake8-main/flake8-main/docs/` - Flake8文档目录
- `flake8-main/flake8-main/tests/` - Flake8测试目录
- `bandit-main/bandit-main/tests/` - Bandit测试目录
- `bandit-main/bandit-main/examples/` - Bandit示例目录
- `bandit-main/bandit-main/doc/` - Bandit文档目录
**注意**:这些是第三方工具的源码,删除后不影响系统运行,但会减少项目体积。
### 2. 输出文件(可选)
- `out/` 目录下的旧报告文件可以定期清理,保留最新的即可
### 3. 空目录
- `scripts/` 目录为空,可以删除(如果存在)
## 清理后的项目结构
```
src/
├── backend.js # 后端主文件
├── frontend/ # 前端文件
├── server/ # 服务器模块
├── config/ # 配置文件目录
├── projects_data/ # 项目数据
├── out/ # 输出报告
├── rule/ # 规则文件
├── pylint-main/ # Pylint源码仅保留核心代码
├── flake8-main/ # Flake8源码仅保留核心代码
├── bandit-main/ # Bandit源码仅保留核心代码
├── README.md # 项目说明
├── 项目功能说明.md # 功能说明
├── 研究背景.md # 研究背景
├── 快速开始指南.md # 快速开始
├── 创新点说明.md # 创新点说明
└── package.json # 依赖配置
```
## 注意事项
1. **不要删除**
- `pylint-main/pylint/` - Pylint核心代码
- `flake8-main/flake8-main/src/flake8/` - Flake8核心代码
- `bandit-main/bandit-main/bandit/` - Bandit核心代码
- 所有配置文件(`package.json`, `requirements.txt`等)
2. **可以删除**
- 第三方工具的文档和测试目录
- 旧的项目报告文件
- 空的目录
3. **建议保留**
- 所有项目文档README、功能说明等
- 核心代码文件
- 配置文件

@ -0,0 +1,6 @@
# 基于AI智能分析与多工具协同的Python代码质量检查系统
## 研究背景
随着Python在军工、金融等关键领域的广泛应用代码质量检查的重要性日益凸显。传统的静态分析工具如Pylint、Flake8、Bandit虽然能有效检测代码问题但存在工具分散、结果重复、缺乏智能分析和详细修复建议等局限性。同时现有研究多集中在单一工具或单一AI模型的应用缺乏多工具协同与AI智能分析的深度融合。因此本研究旨在构建一个基于AI智能分析与多工具协同的Python代码质量检查系统通过集成Pylint、Flake8、Bandit等主流工具实现统一检查平台利用大语言模型进行智能去重、风险评估和详细修复建议生成并支持本地AI模型Ollama和云端AI服务科大讯飞、OpenAI的灵活切换特别针对军工等特殊领域提供定制化的代码合规性检查方案以提升代码质量、提高开发效率、降低安全风险为代码质量检查领域提供新的理论支撑和实践价值。

@ -0,0 +1,172 @@
# 项目功能说明
## 基于AI智能分析与多工具协同的Python代码质量检查系统
### 核心功能
本项目开发了四个核心功能模块:**多工具协同检查**集成Pylint、Flake8、Bandit三大主流Python代码检查工具实现并行执行和结果统一全面覆盖代码质量、编码规范和安全漏洞检测**AI智能分析模块**利用大语言模型对检查结果进行智能去重基于文件路径、行号、规则ID和消息相似度匹配、风险评估划分高、中、低风险等级并给出0-100分评分和详细修复建议生成包含问题分析、修复步骤、代码示例和最佳实践**AI服务支持**提供灵活的AI服务选择支持云端AI服务科大讯飞Spark、OpenAI和本地AI模型Ollama等通过统一接口实现本地与云端AI的无缝切换满足数据安全和离线使用需求**规则集管理**支持自定义规则集配置(.pylintrc、setup.cfg、.flake8等实现规则文件的上传、编辑、应用和管理满足不同行业和项目的特殊合规性要求。这四个核心功能模块协同工作形成了从代码检查、智能分析到规则定制的完整代码质量检查解决方案。
#### 5. 项目管理
**功能描述**:提供完整的项目生命周期管理,支持项目创建、导入、查看和删除。
**主要特性**
- **项目创建**
- 支持从本地文件夹导入项目
- 支持GitHub/Gitee仓库克隆
- 支持文件上传创建项目
- **项目信息**
- 项目基本信息展示(名称、路径、创建时间等)
- 项目文件树浏览
- 项目检查历史记录
- **项目操作**
- 项目删除
- 项目文件管理
- 项目配置管理
#### 6. 在线代码编辑
**功能描述**提供Web端的代码编辑器支持在线查看、编辑和保存代码文件。
**主要特性**
- **文件浏览**
- 树形文件结构展示
- 文件路径导航
- 文件类型过滤
- **代码编辑**
- 在线代码编辑器
- 语法高亮支持Python
- 代码保存功能
- **问题定位**
- 检查结果一键定位到代码位置
- 高亮显示问题代码行
- 快速跳转到问题文件
#### 7. 检查结果展示
**功能描述**:提供直观、详细的检查结果展示,帮助开发者快速理解问题。
**主要特性**
- **结果分类**
- 按问题类型分类(错误、警告、信息)
- 按风险等级分类(高、中、低风险)
- 按文件分组显示
- **详细信息**
- 问题位置(文件、行号、列号)
- 问题规则和消息
- 风险等级和评分
- AI生成的详细修复建议
- **结果筛选**
- 按类型筛选(全部/错误/警告/信息)
- 按风险等级筛选
- 按文件筛选
#### 8. 报告导出
**功能描述**支持将检查结果导出为Markdown格式的报告文件。
**主要特性**
- **报告生成**
- 自动生成Markdown格式报告
- 包含项目信息、检查摘要、问题详情
- 包含AI分析结果和修复建议
- **报告内容**
- 检查结果统计(错误、警告、信息数量)
- AI分析结果去重数量、风险分布
- 详细问题列表(按文件分组)
- 每个问题的完整信息和修复建议
- **报告下载**
- 一键下载报告文件
- 文件名自动包含时间戳
- 支持本地保存和分享
#### 9. 配置中心
**功能描述**集中管理系统配置包括AI配置和本地模型管理。
**主要特性**
- **AI配置管理**
- 选择使用本地AI或云端AI
- 本地模型选择
- 云端AI服务信息显示
- 配置保存和测试连接
- **本地模型管理**
- 本地模型上传和添加
- 模型信息管理名称、描述、API地址
- 模型列表查看
- 模型连接测试
- 模型删除
#### 10. Web可视化平台
**功能描述**提供友好的Web界面支持所有功能的可视化操作。
**主要特性**
- **仪表板**
- 项目统计信息展示
- 合规率、待修复问题、高危漏洞统计
- 最近项目列表
- 快速开始指南
- **用户界面**
- 响应式设计,适配不同屏幕尺寸
- 现代化UI设计操作直观
- 实时进度显示
- 错误提示和成功提示
- **导航系统**
- 顶部导航栏
- 页面切换
- 功能模块快速访问
### 二、技术特性
#### 1. 系统架构
- **前后端分离**前端使用HTML5/CSS3/JavaScript后端使用Node.js/Express
- **模块化设计**:功能模块独立,便于维护和扩展
- **RESTful API**标准化的API接口设计
#### 2. 性能优化
- **并行检查**:多个工具同时运行,提高检查效率
- **批量处理**AI分析支持批量处理所有问题
- **结果缓存**:检查结果缓存,避免重复计算
#### 3. 错误处理
- **容错机制**:工具执行失败时自动回退
- **错误提示**:友好的错误信息提示
- **日志记录**:详细的日志记录,便于问题排查
#### 4. 安全性
- **路径验证**:防止路径遍历攻击
- **文件类型验证**:限制上传文件类型
- **输入验证**API参数验证和过滤
### 三、应用场景
1. **军工代码合规性检查**:针对军工领域对代码质量的特殊要求,提供定制化的代码合规性检查
2. **企业代码质量管控**:帮助企业建立代码质量检查流程,提升代码质量
3. **开源项目维护**:帮助开源项目维护者快速发现和修复代码问题
4. **教学和培训**:作为代码质量检查的教学工具,帮助学生理解代码规范
5. **CI/CD集成**:可集成到持续集成流程中,自动化代码质量检查
### 四、系统优势
1. **多工具协同**:集成多个工具,覆盖代码质量、风格、安全等各个方面
2. **AI智能分析**利用AI技术提供智能去重、风险评估和详细建议
3. **灵活配置**支持自定义规则集和AI服务选择
4. **用户友好**直观的Web界面操作简单便捷
5. **功能完整**:从项目导入到结果导出,提供完整的代码检查流程
---
*本文档基于"基于AI智能分析与多工具协同的Python代码质量检查系统"项目编写*

@ -1,405 +0,0 @@
# FortifyCode 项目集成说明
## 修改概述
本次修改将前端界面与项目后端进行了完整集成,使系统能够正常运行。
## 主要修改内容
### 1. 创建完整的后端服务器 (`backend.js`)
**位置**: `src/backend.js`
**主要功能**:
- 运行在端口 5000与前端期望的端口一致
- 提供所有前端需要的 API 接口(添加了 `/api` 前缀)
- 集成三个代码检查工具Pylint、Flake8、Bandit
- 实现项目管理功能
- 实现文件上传和管理功能
- 提供静态文件服务(前端页面)
**新增 API 端点**:
```
GET /api/health - 健康检查
POST /api/upload - 文件上传
POST /api/check - 代码检查
GET /api/projects - 获取所有项目
POST /api/projects - 创建新项目
GET /api/projects/:id - 获取项目详情
DELETE /api/projects/:id - 删除项目
POST /api/projects/:id/check - 运行项目检查
POST /api/projects/:id/upload-files - 上传文件到项目
GET /api/projects/:id/files - 获取项目文件列表
GET /api/projects/:id/files/content - 获取文件内容
PUT /api/projects/:id/files/content - 保存文件内容
```
### 2. 修改前端 API 配置 (`frontend/js/app.js`)
**修改内容**:
```javascript
// 修改前
const API_BASE_URL = 'http://localhost:5000/api';
// 修改后
const API_BASE_URL = window.location.origin + '/api';
```
**优势**: 自动适配部署环境,无需手动修改配置
### 3. 创建启动脚本
#### Windows 启动脚本 (`start_server.bat`)
- 自动检查 Node.js 和 Python 环境
- 自动安装缺失的依赖
- 自动安装代码检查工具
- 提供友好的启动提示
#### Linux/Mac 启动脚本 (`start_server.sh`)
- 同上,适配 Unix 系统
### 4. 更新项目配置 (`package.json`)
**修改内容**:
- 更新项目名称和描述
- 添加启动脚本
- 设置正确的主入口文件
### 5. 创建文档
#### README.md
- 完整的项目说明
- 安装和使用指南
- API 文档
- 常见问题解答
#### 快速开始指南.md
- 快速上手教程
- 使用示例
- 问题排查
#### requirements.txt
- Python 依赖列表
- 便于一键安装
## 项目结构变化
### 之前的结构问题
```
src/
├── frontend/ # 前端代码
│ ├── index.html
│ ├── css/style.css
│ └── js/app.js # API指向 localhost:5000/api
├── view/
│ └── backend.js # 后端运行在 localhost:3000
│ # 只有 /check 端点,没有 /api 前缀
└── ...
❌ 问题:
1. 端口不匹配 (前端5000, 后端3000)
2. API路径不匹配 (前端需要/api前缀)
3. 缺少大量前端需要的API端点
```
### 修改后的结构
```
src/
├── backend.js # ✅ 新的完整后端服务器
│ # - 端口5000
│ # - /api 前缀
│ # - 所有必要的API端点
├── frontend/ # 前端代码(已集成)
│ ├── index.html
│ ├── css/style.css
│ └── js/app.js # ✅ API自动适配当前域名
├── start_server.bat # ✅ Windows启动脚本
├── start_server.sh # ✅ Linux/Mac启动脚本
├── package.json # ✅ 更新的配置
├── README.md # ✅ 完整文档
├── 快速开始指南.md # ✅ 快速入门
├── requirements.txt # ✅ Python依赖
├── projects_data/ # ✅ 项目数据目录(自动创建)
└── view/ # 旧的后端(保留,可选择性使用)
└── backend.js
✅ 优势:
1. 端口统一为5000
2. API路径统一
3. 功能完整
4. 易于部署和使用
```
## 技术实现细节
### 1. 代码检查工具集成
```javascript
// 工具配置
const TOOL_CONFIG = {
bandit: { ... }, // 安全漏洞检查
flake8: { ... }, // 代码规范检查
pylint: { ... } // 代码质量检查
};
// 执行检查
async function runCodeCheck(filePath) {
// 并行执行三个工具
// 收集和统一处理结果
// 返回统一格式的问题列表
}
```
### 2. 文件上传处理
```javascript
// 使用 Multer 中间件处理文件上传
const upload = multer({
storage: storage,
limits: { fileSize: 100 * 1024 * 1024 }, // 100MB
fileFilter: (req, file, cb) => {
// 只接受 Python 文件
if (file.originalname.match(/\.(py|pyx|pyi)$/)) {
cb(null, true);
}
}
});
```
### 3. 项目数据持久化
```javascript
// 使用 JSON 文件存储项目数据
// 生产环境建议使用数据库
const PROJECTS_FILE = path.join(PROJECTS_DIR, 'projects.json');
function saveProjects() {
fs.writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2));
}
```
### 4. CORS 配置
```javascript
// 允许跨域请求
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
```
## 与原有项目的兼容性
### 保留的内容
1. **前端代码** (`frontend/`)
- 完全保留,只修改了 API_BASE_URL
- HTML、CSS、JavaScript 功能不变
2. **原后端** (`view/backend.js`)
- 保留不动,可作为参考
- 如需使用可独立运行在3000端口
3. **代码检查工具** (bandit-main, flake8-main, pylint-main)
- 保留不动
- 新后端通过 Python 命令调用这些工具
4. **文档和配置** (doc/, model/)
- 完全保留
### 新增的内容
1. `backend.js` - 完整的后端服务器
2. `start_server.bat` - Windows 启动脚本
3. `start_server.sh` - Linux/Mac 启动脚本
4. `README.md` - 项目文档
5. `快速开始指南.md` - 快速入门
6. `requirements.txt` - Python 依赖
7. `项目集成说明.md` - 本文档
## 使用说明
### 启动系统
**方式 1**: 使用启动脚本(推荐)
```cmd
# Windows
start_server.bat
# Linux/Mac
./start_server.sh
```
**方式 2**: 使用 npm
```bash
npm start
```
**方式 3**: 直接运行
```bash
node backend.js
```
### 访问系统
浏览器打开: http://localhost:5000
### 系统流程
```
1. 用户访问 http://localhost:5000
2. Express 服务器返回前端页面
3. 前端调用 /api/* 接口
4. 后端处理请求,调用 Python 检查工具
5. 返回检查结果给前端
6. 前端展示结果
```
## 测试建议
### 1. 环境测试
```bash
# 检查 Node.js
node --version
# 检查 Python
python --version
# 检查检查工具
python -m pylint --version
python -m flake8 --version
python -m bandit --version
```
### 2. 功能测试
1. **文件上传检查**
- 上传一个 Python 文件
- 检查是否正常显示结果
2. **项目管理**
- 创建一个新项目
- 上传文件到项目
- 运行项目检查
3. **文件浏览器**
- 浏览项目文件
- 编辑文件内容
- 保存文件
### 3. API 测试
使用 curl 或 Postman 测试 API 端点:
```bash
# 健康检查
curl http://localhost:5000/api/health
# 获取项目列表
curl http://localhost:5000/api/projects
```
## 部署建议
### 开发环境
- 使用 `node backend.js` 直接运行
- 使用 `nodemon` 实现自动重启
### 生产环境
- 使用 PM2 管理进程
- 配置反向代理Nginx/Apache
- 使用数据库替代 JSON 文件存储
- 添加用户认证和权限管理
## 后续优化建议
### 功能优化
1. **数据库集成**
- 使用 SQLite/MySQL/PostgreSQL 存储项目数据
- 更好的并发性能
2. **用户系统**
- 添加用户注册和登录
- 多用户项目隔离
3. **检查任务队列**
- 使用消息队列处理大量检查任务
- 支持异步检查和结果推送
4. **结果缓存**
- 缓存检查结果
- 避免重复检查相同代码
5. **Git 集成增强**
- 实际实现 GitHub/Gitee 克隆功能
- 支持分支和版本管理
### 性能优化
1. **并发处理**
- 多个文件并行检查
- Worker 线程池
2. **增量检查**
- 只检查变更的文件
- 智能差异分析
3. **前端优化**
- 代码分割和懒加载
- 虚拟滚动优化大量结果显示
## 常见问题
### Q: 为什么有两个 backend.js
A:
- `src/backend.js` - 新的完整后端(推荐使用)
- `src/view/backend.js` - 原有后端(保留作为参考)
建议使用新的 `src/backend.js`
### Q: 可以删除旧的 view/backend.js 吗?
A: 可以。新的 backend.js 已经包含了所有功能。但建议保留一段时间作为备份。
### Q: 如何修改端口?
A: 编辑 `backend.js` 第 10 行:
```javascript
const PORT = process.env.PORT || 5000;
```
或设置环境变量:
```bash
PORT=8080 node backend.js
```
### Q: Python 检查工具安装在哪里?
A: 通过 pip 安装到系统或虚拟环境。后端通过 Python 命令行调用这些工具。
## 总结
本次集成:
- ✅ 统一了前后端接口
- ✅ 完善了所有缺失的功能
- ✅ 提供了完整的文档和启动脚本
- ✅ 保持了代码的可维护性
- ✅ 易于部署和使用
现在可以直接运行 `start_server.bat`Windows`./start_server.sh`Linux/Mac来启动完整的系统。
---
集成完成时间: 2025年10月21日

@ -0,0 +1,310 @@
# 3.3 用例设计
## 3.3.1 "执行代码检测"用例实现的设计方案
"执行代码检测"功能的实现主要是通过"ToolManager"对象提供的服务调用多个代码质量检测工具Bandit、Flake8、Pylint对用户上传的Python代码文件进行分析从而生成详细的检测报告。具体实现过程见图3.3.1 所描述的用例实现顺序图。
**图3.3.1 "执行代码检测"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.1 "执行代码检测"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>DashboardView</u>" as LoginUI
participant "<<controller>>\n<u>BackendService</u>" as LoginManager
participant "<<entity>>\n<u>FileStorageManager</u>" as UserLibrary
participant "<<entity>>\n<u>DetectionTask</u>" as DetectionTask
participant "<<controller>>\n<u>ToolManager</u>" as ToolManager
participant "<<entity>>\n<u>CodeAnalysisEngine</u>" as ExtTool
participant "<<controller>>\n<u>ReportGenerator</u>" as ReportGenerator
用户 -> LoginUI: 1. 输入账号和密码
用户 -> LoginUI: 2. 选择代码文件并上传
用户 -> LoginUI: 3. 选择检测工具
用户 -> LoginUI: 4. 点击"开始检查"按钮
LoginUI -> LoginManager: 5. handleCodeCheck(file, tools)
activate LoginManager
LoginManager -> LoginManager: 6. 验证请求参数
LoginManager -> UserLibrary: 7. saveUploadedFile(file)
activate UserLibrary
UserLibrary -> UserLibrary: 8. isFileValid(file)
UserLibrary --> LoginManager: 9. VerificationResult (文件路径)
deactivate UserLibrary
LoginManager -> DetectionTask: 10. new DetectionTask(filePath, fileName, tools)
activate DetectionTask
DetectionTask -> DetectionTask: 11. 初始化任务属性\n(生成taskId, 设置status=Pending)
DetectionTask --> LoginManager: 12. 返回任务对象
deactivate DetectionTask
LoginManager -> DetectionTask: 13. start()
activate DetectionTask
DetectionTask -> DetectionTask: 14. 设置status=Running\n记录startTime
deactivate DetectionTask
loop 遍历每个选中的工具
LoginManager -> ToolManager: 15. runTool(toolName, filePath)
activate ToolManager
ToolManager -> ToolManager: 16. generateCommand(toolName, filePath)
ToolManager -> ExtTool: 17. executeCommand(command)
activate ExtTool
ExtTool -> ExtTool: 18. 分析代码文件
ExtTool --> ToolManager: 19. 返回原始输出 (stdout)
deactivate ExtTool
ToolManager -> ToolManager: 20. parseToolOutput(toolName, stdout)
ToolManager --> LoginManager: 21. 返回结构化结果
deactivate ToolManager
LoginManager -> DetectionTask: 22. addResult(toolName, result)
activate DetectionTask
DetectionTask -> DetectionTask: 23. 添加结果到results数组
deactivate DetectionTask
end
LoginManager -> ReportGenerator: 24. generateReport(results, filename)
activate ReportGenerator
ReportGenerator -> ReportGenerator: 25. formatToolResult(toolResult)
ReportGenerator --> LoginManager: 26. 返回报告内容
deactivate ReportGenerator
LoginManager -> UserLibrary: 27. saveReport(reportContent, filename)
activate UserLibrary
UserLibrary -> UserLibrary: 28. 保存报告到输出目录
UserLibrary --> LoginManager: 29. 返回报告路径
deactivate UserLibrary
LoginManager -> DetectionTask: 30. complete()
activate DetectionTask
DetectionTask -> DetectionTask: 31. 设置status=Completed\n记录endTime
DetectionTask --> LoginManager: 32. 返回任务结果
deactivate DetectionTask
LoginManager --> LoginUI: 33. CheckResult (检测结果和报告路径)
deactivate LoginManager
LoginUI -> LoginUI: 34. 更新界面显示\n(进度、结果列表)
LoginUI -> 用户: 35. 展示检测结果
@enduml
```
首先,用户通过边界类"DashboardView"对象选择并上传Python代码文件选择要使用的检测工具Bandit、Flake8、Pylint随后该对象向控制类"BackendService"对象发消息"handleCodeCheck(file, tools)",以请求执行代码检测。接收到消息后,"BackendService"对象首先调用实体类"FileStorageManager"对象的方法"saveUploadedFile(file)"以保存上传的文件到临时目录,"FileStorageManager"对象通过自身内部的方法"isFileValid(file)"来判断文件是否有效,并将验证的结果"VerificationResult"返回给"BackendService"对象。
"BackendService"对象收到文件保存成功后,创建实体类"DetectionTask"对象以封装检测任务信息,并调用其"start()"方法将任务状态设置为"Running"。随后,"BackendService"对象遍历用户选中的检测工具,向控制类"ToolManager"对象发消息"runTool(toolName, filePath)"以执行代码检测。"ToolManager"对象通过自身内部的方法"generateCommand(toolName, filePath)"生成命令行命令,并向实体类"CodeAnalysisEngine"对象发消息"executeCommand(command)"以执行代码分析。"CodeAnalysisEngine"对象接收到消息后根据命令类型调用对应的检测工具Bandit、Flake8或Pylint执行代码分析。检测工具完成分析后"CodeAnalysisEngine"对象返回原始输出结果给"ToolManager"对象,"ToolManager"对象通过"parseToolOutput(toolName, stdout)"方法解析原始输出,将其转换为结构化的检测结果,并返回给"BackendService"对象。
"BackendService"对象将每个工具的检测结果添加到"DetectionTask"对象的results数组中。所有工具执行完成后"BackendService"对象向控制类"ReportGenerator"对象发消息"generateReport(results, filename)"以生成检测报告。"ReportGenerator"对象通过自身内部的方法"formatToolResult(toolResult)"格式化每个工具的检测结果生成Markdown格式的报告内容并返回给"BackendService"对象。
随后,"BackendService"对象调用"FileStorageManager"对象的方法"saveReport(reportContent, filename)"保存报告到输出目录,并调用"DetectionTask"对象的"complete()"方法将任务状态设置为"Completed"。"BackendService"对象将包含检测结果和报告路径的"CheckResult"返回给"DashboardView"对象,"DashboardView"对象更新界面显示检测结果列表和统计信息,用户可以在界面上查看详细的检测结果。一旦检测完成,系统将检测结果以列表形式展示给用户,用户可以通过筛选功能查看不同类型的问题(错误、警告、信息)。
## 3.3.2 "创建项目"用例实现的设计方案
"创建项目"功能的实现主要是通过"BackendService"对象提供的服务创建新的代码检查项目记录并根据项目来源类型GitHub、Gitee或文件上传保存相应的项目信息从而使用户能够对不同类型的代码进行分类管理和检查。具体实现过程见图3.3.2 所描述的用例实现顺序图。
**图3.3.2 "创建项目"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.2 "创建项目"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>ProjectManagementView</u>" as SettingUI
participant "<<controller>>\n<u>BackendService</u>" as SettingManager
participant "<<entity>>\n<u>FileStorageManager</u>" as FileStorage
participant "<<entity>>\n<u>Project</u>" as Project
用户 -> SettingUI: 1. 点击"新建项目"按钮
SettingUI -> SettingUI: 2. 显示新建项目对话框
用户 -> SettingUI: 3. 输入项目信息\n(名称、描述)
用户 -> SettingUI: 4. 选择项目来源类型
alt 项目来源为GitHub或Gitee
用户 -> SettingUI: 5. 输入Git URL
SettingUI -> SettingManager: 6. startSetting()
activate SettingManager
SettingManager -> SettingManager: 7. 验证项目信息
SettingManager -> Project: 8. new Project(name, description, sourceType, sourceUrl)
activate Project
Project -> Project: 9. 初始化项目属性\n(生成projectId, 设置创建时间)
Project --> SettingManager: 10. 返回项目对象
deactivate Project
SettingManager -> SettingManager: 11. 保存项目记录
SettingManager --> SettingUI: 12. CreateProjectResult (项目ID和基本信息)
deactivate SettingManager
else 项目来源为文件上传
用户 -> SettingUI: 7. 选择文件或文件夹
SettingUI -> SettingManager: 8. startSetting()
activate SettingManager
SettingManager -> SettingManager: 9. 验证项目信息
SettingManager -> Project: 10. new Project(name, description, sourceType)
activate Project
Project -> Project: 11. 初始化项目属性\n(生成projectId, 设置创建时间)
Project --> SettingManager: 12. 返回项目对象
deactivate Project
SettingManager -> SettingManager: 13. 保存项目记录
SettingManager --> SettingUI: 14. CreateProjectResult (项目ID)
deactivate SettingManager
SettingUI -> SettingManager: 15. uploadProjectFiles(projectId, files)
activate SettingManager
loop 遍历每个文件
SettingManager -> FileStorage: 16. saveUploadedFile(file, projectPath)
activate FileStorage
FileStorage -> FileStorage: 17. 保存文件到项目目录
FileStorage --> SettingManager: 18. 返回文件路径
deactivate FileStorage
end
SettingManager --> SettingUI: 19. UploadResult (上传文件数量)
deactivate SettingManager
endif
SettingUI -> SettingUI: 20. 刷新项目列表
SettingUI -> 用户: 21. 显示新创建的项目
@enduml
```
边界类"ProjectManagementView"对象基于用户输入的项目信息(如项目名称、描述、来源类型等),向控制类"BackendService"对象发消息"startSetting()"以进行项目创建的初始化工作;"BackendService"对象接收到消息后,首先验证用户输入的项目信息是否有效,随后创建实体类"Project"对象并初始化项目属性生成projectId、设置创建时间等至此项目创建的初始化工作已经完成。
如果项目来源类型为GitHub或Gitee用户需要输入Git仓库的URL"BackendService"对象将Git URL信息保存到"Project"对象中,并返回创建结果给"ProjectManagementView"对象。如果项目来源类型为文件上传,用户需要选择本地文件或文件夹,"BackendService"对象创建项目记录后,"ProjectManagementView"对象通过消息"uploadProjectFiles(projectId, files)"将文件上传请求发送给"BackendService"对象,随后"BackendService"对象将文件上传请求转发给实体类"FileStorageManager"对象,请求保存文件到项目的专用目录。"FileStorageManager"对象接收到文件后,将文件保存到对应项目的目录中,并返回文件保存结果。
一旦项目创建任务完成,"BackendService"对象将通过消息"CreateProjectResult"将项目创建结果信息反馈给边界类"ProjectManagementView"对象,"ProjectManagementView"对象刷新项目列表并显示新创建的项目,用户可以在项目管理界面中看到新创建的项目。
## 3.3.3 "查看检测结果"用例实现的设计方案
根据用户选择要查看的检测记录,"ProjectDetailView"对象将向"BackendService"对象请求加载检测结果详情,"BackendService"对象从"DetectionTask"对象中获取存储的检测结果,并通过"ReportGenerator"对象格式化结果数据最终将详细的检测结果展示给用户具体实现过程见图3.3.3 所描述的用例实现顺序图。
**图3.3.3 "查看检测结果"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.3 "查看检测结果"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>ProjectDetailView</u>" as SettingUI
participant "<<controller>>\n<u>BackendService</u>" as SettingManager
participant "<<entity>>\n<u>DetectionTask</u>" as DetectionTask
participant "<<controller>>\n<u>ReportGenerator</u>" as ReportGenerator
participant "<<entity>>\n<u>FileStorageManager</u>" as FileStorage
用户 -> SettingUI: 1. 进入检测结果查看界面
SettingUI -> SettingManager: 2. getCheckHistory(projectId)
activate SettingManager
SettingManager -> SettingManager: 3. 查询检测历史记录
SettingManager --> SettingUI: 4. CheckHistoryList (检测历史列表)
deactivate SettingManager
SettingUI -> SettingUI: 5. 显示检测历史列表
用户 -> SettingUI: 6. 选择要查看的检测记录
SettingUI -> SettingManager: 7. getCheckResult(projectId, checkId)
activate SettingManager
SettingManager -> DetectionTask: 8. getResults(checkId)
activate DetectionTask
DetectionTask -> DetectionTask: 9. 查询检测结果数据
DetectionTask --> SettingManager: 10. 返回检测结果
deactivate DetectionTask
SettingManager -> ReportGenerator: 11. formatResults(results)
activate ReportGenerator
ReportGenerator -> ReportGenerator: 12. 格式化结果数据\n(按类型分类、排序)
ReportGenerator --> SettingManager: 13. 返回格式化后的结果
deactivate ReportGenerator
SettingManager --> SettingUI: 14. CheckResult (问题列表、统计信息)
deactivate SettingManager
SettingUI -> SettingUI: 15. 显示检测结果摘要\n(错误数、警告数等)
SettingUI -> SettingUI: 16. 显示问题列表
用户 -> SettingUI: 17. 选择筛选类型\n(全部/错误/警告/信息)
SettingUI -> SettingUI: 18. 根据类型筛选问题列表
用户 -> SettingUI: 19. 点击问题查看详情
SettingUI -> SettingUI: 20. 显示问题详情和修复建议
用户 -> SettingUI: 21. 点击下载报告按钮
SettingUI -> SettingManager: 22. downloadReport(reportId)
activate SettingManager
SettingManager -> FileStorage: 23. readFile(reportPath)
activate FileStorage
FileStorage -> FileStorage: 24. 读取报告文件内容
FileStorage --> SettingManager: 25. 返回报告内容
deactivate FileStorage
SettingManager --> SettingUI: 26. 返回报告文件
deactivate SettingManager
SettingUI -> 用户: 27. 触发文件下载
@enduml
```
边界类"ProjectDetailView"对象首先向控制类"BackendService"对象发消息"getCheckHistory(projectId)"以获取项目的检测历史记录列表,"BackendService"对象查询数据库中存储的检测任务记录,并将检测历史列表"CheckHistoryList"返回给"ProjectDetailView"对象,"ProjectDetailView"对象在界面上显示检测历史列表供用户选择。
用户选择要查看的检测记录后,"ProjectDetailView"对象向"BackendService"对象发消息"getCheckResult(projectId, checkId)"以请求加载详细的检测结果。"BackendService"对象接收到消息后,向实体类"DetectionTask"对象发消息"getResults(checkId)"以获取存储的检测结果数据。"DetectionTask"对象从其results属性中查询对应的检测结果并将结果返回给"BackendService"对象。
"BackendService"对象接收到检测结果后,向控制类"ReportGenerator"对象发消息"formatResults(results)"以格式化结果数据。"ReportGenerator"对象通过自身内部的方法对检测结果进行格式化处理,包括按问题类型(错误、警告、信息)进行分类、按严重程度排序等,并将格式化后的结果返回给"BackendService"对象。
"BackendService"对象将包含问题列表和统计信息的"CheckResult"返回给"ProjectDetailView"对象,"ProjectDetailView"对象在界面上显示检测结果摘要(错误数、警告数、信息数等)和详细的问题列表。用户可以通过筛选功能选择查看特定类型的问题,"ProjectDetailView"对象根据用户选择的筛选类型过滤问题列表并更新显示。
如果用户需要下载检测报告,"ProjectDetailView"对象向"BackendService"对象发消息"downloadReport(reportId)"以请求下载报告文件。"BackendService"对象调用实体类"FileStorageManager"对象的方法"readFile(reportPath)"读取报告文件内容,并将报告文件返回给"ProjectDetailView"对象,"ProjectDetailView"对象触发浏览器下载文件,用户可以将检测报告保存到本地。
## 3.3.4 "上传规则文件"用例实现的设计方案
"上传规则文件"功能的实现主要是通过"BackendService"对象提供的服务接收用户上传的规则配置文件验证文件格式的有效性并将文件保存到规则集目录中从而使用户能够自定义代码检测规则以满足不同项目或团队的要求。具体实现过程见图3.3.4 所描述的用例实现顺序图。
**图3.3.4 "上传规则文件"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.4 "上传规则文件"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>RuleSetManagementView</u>" as SettingUI
participant "<<controller>>\n<u>BackendService</u>" as SettingManager
participant "<<entity>>\n<u>FileStorageManager</u>" as FileStorage
participant "<<entity>>\n<u>RuleSet</u>" as RuleSet
用户 -> SettingUI: 1. 点击"上传规则文件"按钮
SettingUI -> SettingUI: 2. 打开文件选择对话框
用户 -> SettingUI: 3. 选择规则文件\n(如setup.cfg)
用户 -> SettingUI: 4. 选择规则类型\n(Pylint/Flake8)
SettingUI -> SettingManager: 5. uploadRuleFile(file, ruleType)
activate SettingManager
SettingManager -> SettingManager: 6. 验证文件格式
SettingManager -> SettingManager: 7. isFileValid(file, ruleType)
alt 文件格式有效
SettingManager -> FileStorage: 8. saveRuleFile(file, ruleType)
activate FileStorage
FileStorage -> FileStorage: 9. 保存文件到规则集目录
FileStorage --> SettingManager: 10. 返回文件路径
deactivate FileStorage
SettingManager -> RuleSet: 11. new RuleSet(fileName, filePath, ruleType)
activate RuleSet
RuleSet -> RuleSet: 12. 初始化规则文件属性\n(生成ruleId, 设置更新时间)
RuleSet --> SettingManager: 13. 返回规则对象
deactivate RuleSet
SettingManager -> SettingManager: 14. 保存规则文件元数据
SettingManager --> SettingUI: 15. UploadRuleResult (规则ID和基本信息)
else 文件格式无效
SettingManager --> SettingUI: 16. UploadRuleResult (错误信息)
endif
deactivate SettingManager
SettingUI -> SettingUI: 17. 刷新规则文件列表
SettingUI -> 用户: 18. 显示新上传的规则文件
@enduml
```
边界类"RuleSetManagementView"对象基于用户选择的规则文件和规则类型如Pylint的.pylintrc文件或Flake8的setup.cfg文件向控制类"BackendService"对象发消息"uploadRuleFile(file, ruleType)"以请求上传规则文件;"BackendService"对象接收到消息后,首先通过自身内部的方法"isFileValid(file, ruleType)"验证文件格式是否符合对应的规则类型要求,如果文件格式有效,则向实体类"FileStorageManager"对象发消息"saveRuleFile(file, ruleType)"以保存规则文件到规则集目录。
"FileStorageManager"对象接收到消息后将规则文件保存到对应的规则集目录中如Pylint规则文件保存到pylint_rules目录Flake8规则文件保存到flake8_rules目录并返回文件保存路径给"BackendService"对象。"BackendService"对象收到文件保存成功后,创建实体类"RuleSet"对象以封装规则文件信息,调用其构造函数"new RuleSet(fileName, filePath, ruleType)"初始化规则文件属性生成ruleId、记录文件大小、设置更新时间等并将规则文件元数据保存到系统中。
一旦规则文件上传任务完成,"BackendService"对象将通过消息"UploadRuleResult"将上传结果信息反馈给边界类"RuleSetManagementView"对象如果上传成功则返回规则ID和基本信息如果上传失败则返回错误信息。"RuleSetManagementView"对象刷新规则文件列表并显示新上传的规则文件,用户可以在规则集管理界面中看到新上传的规则文件,并可以对其进行编辑、下载或删除操作。上传的规则文件可以在后续的代码检测中使用,"ToolManager"对象在执行检测时会自动读取并使用相应的规则配置文件。

@ -0,0 +1,525 @@
# 3.4 类设计
## 1精化类间的关系
在分析类图中有一组分析类BackendService、ToolManager、ReportGenerator、FileStorageManager、DetectionTask。在软件设计阶段这些类仍然有意义将成为软件设计模型中的关键设计类。针对这些设计类间关系的精化设计描述如下具体见图3.4.1。
BackendService 类负责协调整个代码检测流程。在具体实现时BackendService 类通过调用 ToolManager 执行代码检测,通过 FileStorageManager 管理文件存储,通过 ReportGenerator 生成检测报告,因而 BackendService 类与 ToolManager、FileStorageManager、ReportGenerator 类之间的语义关系表现为一般的关联关系。DetectionTask 是封装检测任务信息的实体类,因而 BackendService 与 DetectionTask 之间的语义关系表现为组合关系。
BackendService 类需要创建和管理一个或者多个 DetectionTask 类对象,每个 DetectionTask 类对象只能交由一个 BackendService 类对象进行处理,因而 BackendService 类与 DetectionTask 类之间存在一对多的关系。
**图3.4.1 精化核心业务类间以及它们与"BackendService"类间的关系**
```plantuml
@startuml
title 图3.4.1 精化核心业务类间以及它们与"BackendService"类间的关系
class BackendService {
- app: Express
- PORT: Number
- upload: Multer
- TOOL_CONFIG: Object
+ constructor()
+ start()
+ handleHealthCheck(req, res)
+ handleCodeCheck(req, res)
}
class ToolManager {
- toolConfig: Object
- results: Array
+ constructor()
+ runTool(toolName, filePath): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
}
class ReportGenerator {
- reportTemplate: String
- outputDir: String
+ constructor(outputDir)
+ generateReport(results, filename): String
+ saveReport(reportContent, filename): String
}
class FileStorageManager {
- tempDir: String
- outputDir: String
- uploadConfig: Object
+ constructor(tempDir, outputDir)
+ saveUploadedFile(file): String
+ readFile(filePath): String
+ saveReport(reportContent, filename): void
}
class DetectionTask {
- taskId: String
- status: String
- filePath: String
- fileName: String
- selectedTools: Array
- results: Array
+ constructor(filePath, fileName, selectedTools)
+ start(): void
+ addResult(toolName, result): void
+ complete(): void
+ fail(error): void
}
BackendService "1" -- "1" ToolManager : 关联 (调用)
BackendService "1" -- "1" FileStorageManager : 关联 (调用)
BackendService "1" -- "1" ReportGenerator : 关联 (调用)
BackendService "1" -- "*" DetectionTask : 组合 (创建和管理)
@enduml
```
## 2精化用户界面类间的关系
根据§3.2 节的用户界面设计本系统在Web端包含 DashboardView、ProjectManagementView、ProjectDetailView 和 RuleSetManagementView 等一组界面以支持用户的操作。显然这些用户界面类之间具有如图3.4.2 所描述的关联关系。
**图3.4.2 精化用户界面类间的关系**
```plantuml
@startuml
title 图3.4.2 精化用户界面类间的关系
class DashboardView {
+ displayStats()
+ handleFileUpload()
+ startCheck()
+ stopCheck()
}
class ProjectManagementView {
+ displayProjectList()
+ createProject()
+ searchProject()
+ deleteProject()
}
class ProjectDetailView {
+ displayProjectInfo()
+ displayCheckHistory()
+ viewCodeFile()
+ viewCheckResult()
}
class RuleSetManagementView {
+ uploadRuleFile()
+ displayRuleList()
+ editRule()
+ deleteRule()
}
DashboardView --> ProjectManagementView : 导航到
DashboardView --> RuleSetManagementView : 导航到
ProjectManagementView --> ProjectDetailView : 查看详情
ProjectDetailView --> DashboardView : 返回
@enduml
```
## 3精化关键设计类间的关系
根据§3.3 节的用例设计,"执行代码检测"用例是本系统的核心用例。该用例涉及 BackendService、FileStorageManager、ToolManager、ReportGenerator 和 DetectionTask 五个关键设计类。根据§3.3 所描述的顺序图中五个类间的交互, BackendService 类与 FileStorageManager 类之间具有直接关联关系BackendService 类与 ToolManager 类之间具有直接关联关系ToolManager 类与 ReportGenerator 类之间是依赖关系这些类的关系精化如图3.4.3所示。
**图3.4.3 精化"执行代码检测"用例中类间的关系**
```plantuml
@startuml
title 图3.4.3 精化"执行代码检测"用例中类间的关系
class BackendService {
+ handleCodeCheck(req, res)
}
class FileStorageManager {
+ saveUploadedFile(file): String
+ saveReport(reportContent, filename): void
}
class ToolManager {
+ runTool(toolName, filePath): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
}
class ReportGenerator {
+ generateReport(results, filename): String
}
class DetectionTask {
+ constructor(filePath, fileName, selectedTools)
+ start(): void
+ addResult(toolName, result): void
+ complete(): void
}
BackendService "1" -- "1" FileStorageManager : 关联 (保存文件)
BackendService "1" -- "1" ToolManager : 关联 (调度工具)
BackendService "1" -- "1" DetectionTask : 关联 (创建/管理任务)
ToolManager ..> ReportGenerator : 依赖 (生成报告)
FileStorageManager ..> DetectionTask : 依赖 (存储任务相关文件)
@enduml
```
## 4精化DetectionTask 类属性的设计
DetectionTask 类属性的精化设计描述如下。
1有六项基本属性任务ID"`taskId`"、文件路径"`filePath`"、文件名"`fileName`"、选中的工具"`selectedTools`"、任务状态"`status`"和检测结果"`results`"。它们的类型分别为`String`、`String`、`String`、`Array`、`String`和`Array`。
2这些属性是任务的核心标识和状态信息对外部其他类不可见这六项属性的可见范围为"`private`"。
3`taskId` 在构造时自动生成,`status` 初始值为`"Pending"``results` 初始值为空数组,其他属性在构造时传入。
## 5精化DashboardView 类属性的设计
在用户界面设计中,有"DashboardView"界面用于支持用户上传代码文件并启动代码检测具体见图3.2.1 所示。"DashboardView"界面类属性的精化设计描述如下。
-有一组属性分别对应于界面中的静态元素、用户输入元素和命令界面元素,具体包括:"logo"为界面标识图标,其类型为静态元素,如图标;"uploadArea"为文件上传区域,其类型为用户输入元素,如文件选择框;"fileInput"为用户选择的文件,其类型为用户输入元素,如文件输入框;"startCheckBtn"旨在开始代码检测,其类型为命令界面元素,如按钮;"stopCheckBtn"旨在停止代码检测,其类型为命令界面元素,如按钮;"progressContainer"旨在显示检测进度,其类型为静态元素,如进度条;"resultsSection"旨在显示检测结果,其类型为静态元素,如结果列表。
-这些属性对外部其他类均不可见,它们的可见范围设置为"`private`"。
-"startCheckBtn"和"stopCheckBtn"二个属性的初始值不为空,需要将其预先设置为相应界面的按钮元素。
## 6精化ToolManager 类属性的设计
ToolManager 是"代码检测服务"子系统中的一个重要控制类。根据该子系统中用例实现的交互图ToolManager 类至少有二项基本属性:"toolConfig"以表示工具配置映射表、"results"以表示工具执行结果集合。具体的ToolManager 类属性的精化设计描述如下。
-`private Object toolConfig`,表示工具配置映射表,包含每个工具的命令、参数生成函数和结果解析器
-`private Array results`,表示工具执行结果集合,用于临时存储各工具的原始输出
-这些属性对外部其他类均不可见,它们的可见范围设置为"`private`"。
-`toolConfig` 在构造时根据预设配置初始化,`results` 初始值为空数组。
## 7精化"代码检测服务"子系统中部分类方法的设计
根据控制类 BackendService 的职责,它有一个 `public` 方法 `handleCodeCheck(req, res)` 用于处理代码检测请求。此外,为了在代码检测之前检查上传文件的合法性,它有一个 `private` 方法 `validateFile(req)`,专门用于判断上传的文件是否满足相关的规范和要求。
该方法的主要功能是要依据用户上传的文件和选中的工具判断是否可以启动检测任务,为此可以设计一个 `private` 的方法 `validateRequest(req)`,专门用于判断请求的合法性。
ToolManager 实体类负责管理系统中的代码检测工具,它有一系列的 `public` 方法以实现对工具的管理,包括 `runTool(toolName, filePath)` 方法实现执行指定的代码检测工具,`parseToolOutput(toolName, stdout)` 方法实现解析工具的原始输出,`getAvailableTools()` 方法实现获取所有可用的工具列表,`validateTool(toolName)` 方法以判断工具名称是否有效。此外ToolManager 类还具有创建方法 `constructor()` 和初始化方法 `initToolConfig()` 以便建立工具配置。
**图3.4.4 精化设计BackendService、ToolManager、ReportGenerator 等类的方法**
```plantuml
@startuml
title 图3.4.4 精化设计BackendService、ToolManager、ReportGenerator 等类的方法
class BackendService {
- app: Express
- PORT: Number = 3000
- upload: Multer
- TOOL_CONFIG: Object
+ constructor()
- configureCORS()
- configureUpload()
- setupRoutes()
+ start()
+ handleHealthCheck(req, res)
+ handleCodeCheck(req, res)
- validateFile(req): Boolean
- validateRequest(req): Boolean
}
class ToolManager {
- toolConfig: Object
- results: Array
+ constructor()
+ initToolConfig()
+ runTool(toolName, filePath): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
+ getAvailableTools(): Array
+ validateTool(toolName): Boolean
}
class ReportGenerator {
- reportTemplate: String
- outputDir: String
+ constructor(outputDir)
+ generateReport(results, filename): String
+ saveReport(reportContent, filename): String
- formatToolResult(toolResult): String
- ensureOutputDir()
}
class FileStorageManager {
- tempDir: String
- outputDir: String
- uploadConfig: Object
+ constructor(tempDir, outputDir)
+ saveUploadedFile(file): String
+ readFile(filePath): String
+ saveReport(reportContent, filename): void
+ deleteTempFile(filePath): void
- ensureDirectory(dirPath)
}
@enduml
```
## 8精化"代码检测服务"子系统CodeCheckService中部分类方法的设计
**图3.4.5 "CodeCheckService"子系统的设计类图**
图3.4.5 描述了"代码检测服务"子系统中实现代码检测的一组对象类。根据该子系统的用例实现交互图,可以对子系统中 ToolManager 和 DetectionTask 两个类的方法进行精化设计。首先,针对 DetectionTask 类的六个属性,分别设计相关的方法以设置和获取六个属性的取值;其次,针对 ToolManager 类的方法,详细设计其参数信息。
-`public void setTaskId(String taskId)`,设置任务的唯一标识符
-`public void setStatus(String status)`,设置任务的当前状态
-`public void setFilePath(String filePath)`,设置待检测文件的路径
-`public void setFileName(String fileName)`,设置原始文件名
-`public void setSelectedTools(Array selectedTools)`,设置选中的检测工具列表
-`public void addResult(String toolName, Object result)`,添加工具的检测结果
-`public String getTaskId()`,获取任务的唯一标识符
-`public String getStatus()`,获取任务的当前状态
-`public String getFilePath()`,获取待检测文件的路径
-`public String getFileName()`,获取原始文件名
-`public Array getSelectedTools()`,获取选中的检测工具列表
-`public Array getResults()`,获取所有检测结果
-ToolManager 类的方法 `runTool(toolName, filePath)` 精化如下: `public Promise<Object> runTool(String toolName, String filePath, Object config)`
```plantuml
@startuml
title 图3.4.5 "CodeCheckService"子系统的设计类图
class DetectionTask {
- taskId: String
- status: String
- filePath: String
- fileName: String
- selectedTools: Array
- results: Array
+ constructor(filePath, fileName, selectedTools)
+ setTaskId(taskId)
+ setStatus(status)
+ setFilePath(filePath)
+ setFileName(fileName)
+ setSelectedTools(selectedTools)
+ addResult(toolName, result)
+ getTaskId(): String
+ getStatus(): String
+ getFilePath(): String
+ getFileName(): String
+ getSelectedTools(): Array
+ getResults(): Array
}
class ToolManager {
- toolConfig: Object
- results: Array
+ constructor()
+ runTool(toolName, filePath, config): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
}
DetectionTask --> ToolManager : 调用
@enduml
```
## 9精化BackendService 类中"handleCodeCheck()"方法的实现算法设计
图3.4.6 用UML 活动图描述了 BackendService 类中 `handleCodeCheck()` 方法的精化设计,它定义了该方法的接口 `public handleCodeCheck(req, res)`,描述了其内部的实现算法:首先判断上传文件和检测参数二个输入的参数是否为空,如果为空则返回错误;如果不为空,则调用 FileStorageManager 对象的方法 `saveUploadedFile()` 以保存上传的文件,如果保存成功则创建 DetectionTask 对象并调用 ToolManager 对象的方法 `runTool()` 以执行代码检测,如果检测成功则调用 ReportGenerator 对象的方法 `generateReport()` 以生成检测报告,否则返回检测失败。
**图3.4.6 精化BackendService 类中handleCodeCheck()方法的详细设计**
```plantuml
@startuml
title 图3.4.6 精化BackendService 类中handleCodeCheck()方法的详细设计
start
:接收HTTP请求 (req, res);
:获取上传文件和检测参数;
if (文件为空?) then (是)
:返回错误响应 (400 Bad Request);
stop
else (否)
if (检测参数为空?) then (是)
:返回错误响应 (400 Bad Request);
stop
else (否)
:调用FileStorageManager.saveUploadedFile()保存文件;
if (文件保存成功?) then (是)
:创建DetectionTask对象;
:调用DetectionTask.start()启动任务;
:调用ToolManager.runTool()执行检测;
if (检测执行成功?) then (是)
:收集所有工具的检测结果;
:调用ReportGenerator.generateReport()生成报告;
:调用FileStorageManager.saveReport()保存报告;
:更新DetectionTask状态为Completed;
:返回成功响应 (报告路径);
else (否)
:更新DetectionTask状态为Failed;
:返回失败响应 (错误信息);
endif
else (否)
:返回文件保存失败响应;
endif
endif
endif
stop
@enduml
```
## 10精化ToolManager 类中"runTool()"方法的实现算法设计
图3.4.7用UML 的活动图描述了 ToolManager 类中 `runTool()` 方法的详细设计,它定义了该方法的接口 `public Promise<Object> runTool(toolName, filePath)`,描述了其内部的实现算法:首先根据工具名称获取工具配置,如果找不到配置则返回错误;如果找到配置,则根据工具类型和文件路径生成命令行命令,接着执行外部命令行工具进行检测,在命令执行过程中捕获标准输出和标准错误,并在命令执行完成后解析原始输出,如果解析成功则返回结构化结果,否则返回解析错误。
**图3.4.7 精化ToolManager 类中runTool()方法的详细设计**
```plantuml
@startuml
title 图3.4.7 精化ToolManager 类中runTool()方法的详细设计
start
:接收工具名称 (toolName) 和文件路径 (filePath);
:根据toolName获取工具配置 (toolConfig);
if (找到工具配置?) then (否)
:返回错误 ("不支持的工具");
stop
else (是)
:根据工具配置生成命令行命令;
if (工具是flake8?) then (是)
:创建本地文件副本;
:使用本地文件路径;
endif
:执行外部命令行工具;
:捕获标准输出和标准错误;
if (命令执行成功?) then (是)
:调用parseToolOutput()解析原始输出;
if (解析成功?) then (是)
:构造结构化结果对象;
:返回检测结果;
else (否)
:返回解析失败错误;
endif
else (否)
:返回工具执行失败错误;
endif
endif
stop
@enduml
```
## 11构造类的状态图和活动图
### 图3.4.8 DetectionTask 类对象的状态图
如果一个类的对象具有较为复杂的状态在其生命周期中需要针对外部和内部事件实施一系列的活动以变迁其状态那么可以考虑构造和绘制类的状态图。DetectionTask 类对象具有较为复杂的状态,它创建时将处于空闲状态"Pending",一旦开始执行代码检测时将处于运行状态"Running"以执行检测工具,如果所有工具执行成功则进入到完成状态"Completed",如果检测失败则进入到失败状态"Failed"图3.4.8 描述了 DetectionTask 类对象的状态图。
**图3.4.8 DetectionTask 类对象的状态图**
```plantuml
@startuml
title 图3.4.8 DetectionTask 类对象的状态图
state "Pending\n(待处理)" as Pending
state "Running\n(运行中)" as Running
state "Completed\n(已完成)" as Completed
state "Failed\n(失败)" as Failed
[*] --> Pending : 创建任务
Pending --> Running : start()\n开始检测
Running --> Completed : complete()\n所有工具执行成功
Running --> Failed : fail()\n检测失败
Completed --> [*] : 任务归档
Failed --> [*] : 任务归档
@enduml
```
### 图3.4.9 用户代码检测流程的活动图
如果某个类在实现其职责过程中需要执行一系列的方法、与其他的对象进行诸多的交互那么可以考虑构造和绘制针对该类某些职责的活动图。图3.4.9用UML 的活动图及泳道机制描述了 DashboardView、BackendService、FileStorageManager、ToolManager、ReportGenerator 和 DetectionTask 六个类对象之间如何通过交互和协作来实现用户代码检测的功能。
**图3.4.9 用户代码检测流程的活动图**
```plantuml
@startuml
title 图3.4.9 用户代码检测流程的活动图
|用户|
start
:上传代码文件;
:选择检测工具;
:点击"开始检查"按钮;
|DashboardView|
:收集文件和参数;
:发送检测请求 (HTTP POST);
|BackendService|
:接收HTTP请求;
:调用FileStorageManager.saveUploadedFile();
|FileStorageManager|
:保存文件到本地临时目录;
:返回文件路径;
|BackendService|
:创建DetectionTask对象;
:设置任务状态为Pending;
:调用DetectionTask.start();
:更新任务状态为Running;
|BackendService|
:遍历选中的工具;
while (还有工具未执行?) is (是)
|ToolManager|
:调用ToolManager.runTool();
:执行外部命令行工具;
:解析工具输出;
:返回结构化结果;
|BackendService|
:调用DetectionTask.addResult();
:添加工具检测结果;
endwhile (否)
|BackendService|
:调用ReportGenerator.generateReport();
|ReportGenerator|
:生成Markdown格式报告;
:返回报告内容;
|BackendService|
:调用FileStorageManager.saveReport();
|FileStorageManager|
:保存报告到输出目录;
:返回报告路径;
|BackendService|
:调用DetectionTask.complete();
:更新任务状态为Completed;
:返回检测结果和报告路径;
|DashboardView|
:接收检测结果;
:更新界面显示 (进度、结果列表);
:提供下载报告链接;
|用户|
:查看检测报告;
stop
@enduml
```
Loading…
Cancel
Save