功能完善 #10

Merged
pu8crm6xf merged 1 commits from zhangyang into main 1 month ago

@ -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`
---
**提示**:如果遇到问题,请检查服务器控制台的错误日志,通常会有详细的错误信息。

@ -96,6 +96,70 @@ function saveProjects() {
// 初始化
loadProjects();
// 配置存储目录
const CONFIG_DIR = path.join(__dirname, 'config');
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}
// 本地模型存储目录
const LOCAL_MODELS_DIR = path.join(__dirname, 'local_models');
if (!fs.existsSync(LOCAL_MODELS_DIR)) {
fs.mkdirSync(LOCAL_MODELS_DIR, { recursive: true });
}
// 配置文件路径
const CONFIG_FILE = path.join(CONFIG_DIR, 'ai_config.json');
const LOCAL_MODELS_FILE = path.join(CONFIG_DIR, 'local_models.json');
// 加载配置
function loadConfig() {
try {
if (fs.existsSync(CONFIG_FILE)) {
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
return JSON.parse(data);
}
} catch (error) {
console.error('加载配置失败:', error);
}
return { useLocalAi: false, selectedModelId: null };
}
// 保存配置
function saveConfig(config) {
try {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
return true;
} catch (error) {
console.error('保存配置失败:', error);
return false;
}
}
// 加载本地模型列表
function loadLocalModels() {
try {
if (fs.existsSync(LOCAL_MODELS_FILE)) {
const data = fs.readFileSync(LOCAL_MODELS_FILE, 'utf8');
return JSON.parse(data);
}
} catch (error) {
console.error('加载本地模型列表失败:', error);
}
return [];
}
// 保存本地模型列表
function saveLocalModels(models) {
try {
fs.writeFileSync(LOCAL_MODELS_FILE, JSON.stringify(models, null, 2));
return true;
} catch (error) {
console.error('保存本地模型列表失败:', error);
return false;
}
}
// 获取规则集文件路径
function getRuleSetPath(ruleSetName, tool) {
if (!ruleSetName || !fs.existsSync(RULE_DIR)) {
@ -1209,6 +1273,448 @@ app.put('/api/projects/:id/files/content', (req, res) => {
}
});
// ==================== 本地模型管理API ====================
// 注意:这些路由必须在静态文件服务之前,否则会被拦截
// 获取本地模型列表
app.get('/api/local-models', (req, res) => {
try {
const models = loadLocalModels();
res.json({
success: true,
models: models
});
} catch (error) {
console.error('获取本地模型列表失败:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// 上传/添加本地模型
app.post('/api/local-models', upload.single('file'), (req, res) => {
try {
const { name, description, apiUrl } = req.body;
if (!name || !apiUrl) {
return res.status(400).json({
success: false,
error: '模型名称和API地址不能为空'
});
}
const models = loadLocalModels();
const modelId = `model_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const model = {
id: modelId,
name: name,
description: description || '',
apiUrl: apiUrl,
createdAt: new Date().toISOString(),
filePath: null
};
// 如果有上传文件,保存文件
if (req.file) {
const modelFileDir = path.join(LOCAL_MODELS_DIR, modelId);
if (!fs.existsSync(modelFileDir)) {
fs.mkdirSync(modelFileDir, { recursive: true });
}
const filePath = path.join(modelFileDir, req.file.originalname);
fs.renameSync(req.file.path, filePath);
model.filePath = filePath;
}
models.push(model);
if (saveLocalModels(models)) {
res.json({
success: true,
message: '模型添加成功',
model: model
});
} else {
res.status(500).json({
success: false,
error: '保存模型信息失败'
});
}
} catch (error) {
console.error('添加本地模型失败:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// 测试本地模型连接
app.post('/api/local-models/:id/test', async (req, res) => {
try {
const modelId = req.params.id;
console.log(`[测试模型] 开始测试模型: ${modelId}`);
const models = loadLocalModels();
console.log(`[测试模型] 当前模型列表数量: ${models.length}`);
const model = models.find(m => m.id === modelId);
if (!model) {
console.error(`[测试模型] 模型不存在: ${modelId}`);
return res.status(404).json({
success: false,
error: '模型不存在'
});
}
console.log(`[测试模型] 找到模型: ${model.name}, API地址: ${model.apiUrl}`);
// 测试本地模型连接(发送一个简单的测试请求)
const http = require('http');
const url = require('url');
let apiUrl;
try {
apiUrl = new URL(model.apiUrl);
} catch (error) {
console.error(`[测试模型] API地址格式错误: ${model.apiUrl}`, error);
return res.status(400).json({
success: false,
error: 'API地址格式错误: ' + error.message
});
}
// 确保使用正确的OpenAI兼容端点
let apiPath = apiUrl.pathname;
if (!apiPath.endsWith('/chat/completions')) {
// 如果路径是 /v1自动添加 /chat/completions
if (apiPath.endsWith('/v1') || apiPath === '/v1') {
apiPath = '/v1/chat/completions';
} else if (apiPath.endsWith('/v1/')) {
apiPath = '/v1/chat/completions';
} else if (!apiPath || apiPath === '/') {
apiPath = '/v1/chat/completions';
}
}
console.log(`[测试模型] 使用路径: ${apiPath}`);
console.log(`[测试模型] 模型名称: ${model.name}`);
const testData = JSON.stringify({
model: model.name,
messages: [
{ role: 'user', content: 'test' }
],
max_tokens: 10
});
const options = {
hostname: apiUrl.hostname,
port: apiUrl.port || (apiUrl.protocol === 'https:' ? 443 : 80),
path: apiPath + (apiUrl.search || ''),
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(testData)
},
timeout: 10000 // 增加到10秒
};
console.log(`[测试模型] 发送请求到: ${apiUrl.protocol}//${apiUrl.hostname}:${options.port}${options.path}`);
const testPromise = new Promise((resolve, reject) => {
const protocol = apiUrl.protocol === 'https:' ? require('https') : http;
const req = protocol.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(true);
} else {
console.error(`[测试模型] HTTP错误: ${res.statusCode} ${res.statusMessage}`);
console.error(`[测试模型] 响应内容:`, responseData.substring(0, 500));
reject(new Error(`HTTP ${res.statusCode}: ${responseData.substring(0, 200)}`));
}
});
});
req.on('error', (error) => {
console.error('[测试模型] 请求错误:', error);
reject(error);
});
req.on('timeout', () => {
req.destroy();
reject(new Error('连接超时'));
});
req.write(testData);
req.end();
});
await testPromise;
console.log(`[测试模型] 测试成功: ${modelId}`);
res.json({
success: true,
message: '模型连接测试成功'
});
} catch (error) {
console.error(`[测试模型] 测试失败: ${error.message}`);
console.error(`[测试模型] 错误堆栈:`, error.stack);
res.status(500).json({
success: false,
error: '连接测试失败: ' + error.message
});
}
});
// 删除本地模型
app.delete('/api/local-models/:id', (req, res) => {
try {
const modelId = req.params.id;
const models = loadLocalModels();
const modelIndex = models.findIndex(m => m.id === modelId);
if (modelIndex === -1) {
return res.status(404).json({
success: false,
error: '模型不存在'
});
}
const model = models[modelIndex];
// 删除模型文件目录
if (model.filePath) {
const modelDir = path.dirname(model.filePath);
if (fs.existsSync(modelDir)) {
fs.rmSync(modelDir, { recursive: true, force: true });
}
}
// 从列表中移除
models.splice(modelIndex, 1);
if (saveLocalModels(models)) {
res.json({
success: true,
message: '模型删除成功'
});
} else {
res.status(500).json({
success: false,
error: '保存模型列表失败'
});
}
} catch (error) {
console.error('删除本地模型失败:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// ==================== 配置管理API ====================
// 注意:这些路由必须在静态文件服务之前,否则会被拦截
// 获取配置
app.get('/api/config', (req, res) => {
try {
const config = loadConfig();
const cloudProvider = process.env.AI_PROVIDER || 'xf';
const providerName = cloudProvider === 'xf' ? '科大讯飞 Spark' : cloudProvider === 'openai' ? 'OpenAI' : '未配置';
res.json({
success: true,
config: config,
cloudProvider: providerName
});
} catch (error) {
console.error('获取配置失败:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// 保存配置
app.post('/api/config', (req, res) => {
try {
const { useLocalAi, selectedModelId } = req.body;
if (useLocalAi && !selectedModelId) {
return res.status(400).json({
success: false,
error: '使用本地AI时必须选择模型'
});
}
const config = {
useLocalAi: useLocalAi || false,
selectedModelId: useLocalAi ? selectedModelId : null
};
if (saveConfig(config)) {
res.json({
success: true,
message: '配置保存成功',
config: config
});
} else {
res.status(500).json({
success: false,
error: '保存配置失败'
});
}
} catch (error) {
console.error('保存配置失败:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// 测试配置连接
app.post('/api/config/test', async (req, res) => {
try {
console.log('[测试配置] 收到测试请求');
const { useLocalAi, selectedModelId } = req.body;
console.log('[测试配置] 请求参数:', { useLocalAi, selectedModelId });
if (useLocalAi) {
if (!selectedModelId) {
console.error('[测试配置] 未选择本地模型');
return res.status(400).json({
success: false,
error: '请选择本地模型'
});
}
const models = loadLocalModels();
console.log(`[测试配置] 当前模型列表数量: ${models.length}`);
const model = models.find(m => m.id === selectedModelId);
if (!model) {
console.error(`[测试配置] 模型不存在: ${selectedModelId}`);
return res.status(404).json({
success: false,
error: '模型不存在'
});
}
console.log(`[测试配置] 找到模型: ${model.name}, API地址: ${model.apiUrl}`);
// 测试本地模型连接(发送一个简单的测试请求)
const http = require('http');
const url = require('url');
let apiUrl;
try {
apiUrl = new URL(model.apiUrl);
} catch (error) {
console.error(`[测试配置] API地址格式错误: ${model.apiUrl}`, error);
return res.status(400).json({
success: false,
error: 'API地址格式错误: ' + error.message
});
}
// 确保使用正确的OpenAI兼容端点
let apiPath = apiUrl.pathname;
if (!apiPath.endsWith('/chat/completions')) {
// 如果路径是 /v1自动添加 /chat/completions
if (apiPath.endsWith('/v1') || apiPath === '/v1') {
apiPath = '/v1/chat/completions';
} else if (apiPath.endsWith('/v1/')) {
apiPath = '/v1/chat/completions';
} else if (!apiPath || apiPath === '/') {
apiPath = '/v1/chat/completions';
}
}
console.log(`[测试配置] 使用路径: ${apiPath}`);
console.log(`[测试配置] 模型名称: ${model.name}`);
const testData = JSON.stringify({
model: model.name,
messages: [
{ role: 'user', content: 'test' }
],
max_tokens: 10
});
const options = {
hostname: apiUrl.hostname,
port: apiUrl.port || (apiUrl.protocol === 'https:' ? 443 : 80),
path: apiPath + (apiUrl.search || ''),
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(testData)
},
timeout: 10000 // 增加到10秒
};
console.log(`[测试配置] 发送请求到: ${apiUrl.protocol}//${apiUrl.hostname}:${options.port}${options.path}`);
const testPromise = new Promise((resolve, reject) => {
const protocol = apiUrl.protocol === 'https:' ? require('https') : http;
const req = protocol.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(true);
} else {
console.error(`[测试配置] HTTP错误: ${res.statusCode} ${res.statusMessage}`);
console.error(`[测试配置] 响应内容:`, responseData.substring(0, 500));
reject(new Error(`HTTP ${res.statusCode}: ${responseData.substring(0, 200)}`));
}
});
});
req.on('error', (error) => {
console.error('[测试配置] 请求错误:', error);
reject(error);
});
req.on('timeout', () => {
req.destroy();
reject(new Error('连接超时'));
});
req.write(testData);
req.end();
});
await testPromise;
console.log(`[测试配置] 测试成功`);
res.json({
success: true,
message: '本地模型连接测试成功'
});
} else {
// 测试云端AI连接
console.log('[测试配置] 使用云端AI跳过连接测试');
res.json({
success: true,
message: '云端AI配置通过环境变量设置请检查环境变量'
});
}
} catch (error) {
console.error(`[测试配置] 测试失败: ${error.message}`);
console.error(`[测试配置] 错误堆栈:`, error.stack);
res.status(500).json({
success: false,
error: '连接测试失败: ' + error.message
});
}
});
// 注意本地模型管理API已在第1276行定义静态文件服务之前此处不再重复定义
// 静态文件服务 - 前端
app.use(express.static(path.join(__dirname, 'frontend')));
@ -1234,6 +1740,8 @@ app.use((err, req, res, next) => {
});
});
// 注意配置管理API已在第1486行定义静态文件服务之前此处不再重复定义
// 启动服务器
app.listen(PORT, () => {
console.log('=================================');

@ -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 {

@ -242,6 +242,159 @@
</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 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>
<!-- 本地模型管理面板 -->
<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 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="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>
<!-- 项目管理页面 -->
<div id="projects-page" class="page-content">
<div class="page-header fade-in">
@ -406,7 +559,10 @@
<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;">
<textarea id="modalCodeEditor" placeholder="选择文件开始编辑..." style="flex:1; width:100%; border:none; padding:15px; font-family:'Courier New', monospace; font-size:14px; line-height:1.6; resize:none; outline:none; background:#fafafa; color:#333;"></textarea>
<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>

File diff suppressed because it is too large Load Diff

@ -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"
}
]
```

@ -15,7 +15,7 @@ class AIAnalyzer {
this.openaiApiKey = process.env.OPENAI_API_KEY || '';
this.openaiApiBase = process.env.OPENAI_API_BASE || 'https://api.openai.com/v1';
this.openaiModel = process.env.OPENAI_MODEL || 'gpt-3.5-turbo';
// 科大讯飞配置HTTP API
// 注意HTTP API使用APIpasswordBearer Token不是apikey和apisecret
// 获取地址https://console.xfyun.cn/services/bmx1
@ -24,13 +24,20 @@ class AIAnalyzer {
this.xfApiSecret = process.env.XF_API_SECRET || ''; // 兼容旧配置WebSocket使用
this.xfModel = process.env.XF_MODEL || 'spark-x'; // Spark X1.5 模型使用spark-x
this.xfApiUrl = process.env.XF_API_URL || 'https://spark-api-open.xf-yun.com/v2/chat/completions';
// 选择使用的API提供商
this.provider = process.env.AI_PROVIDER || 'xf'; // 'openai' 或 'xf'
this.enabled = process.env.AI_ANALYZER_ENABLED !== 'false';
console.log(`[AI分析] 使用提供商: ${this.provider}`);
if (this.provider === 'xf') {
// 本地模型配置
this.useLocalAi = false;
this.localModel = null;
this.loadLocalAiConfig();
console.log(`[AI分析] 使用提供商: ${this.useLocalAi ? '本地模型' : this.provider}`);
if (this.useLocalAi && this.localModel) {
console.log(`[AI分析] 本地模型: ${this.localModel.name} (${this.localModel.apiUrl})`);
} else if (this.provider === 'xf') {
if (this.xfApiPassword) {
console.log(`[AI分析] 科大讯飞 HTTP API Password: ${this.xfApiPassword.substring(0, 10)}... (已配置)`);
console.log(`[AI分析] 使用模型: ${this.xfModel}`);
@ -43,6 +50,36 @@ class AIAnalyzer {
}
}
/**
* 加载本地AI配置
*/
loadLocalAiConfig() {
try {
const configPath = path.join(__dirname, '..', '..', 'config', 'ai_config.json');
if (fs.existsSync(configPath)) {
const configData = fs.readFileSync(configPath, 'utf8');
const config = JSON.parse(configData);
this.useLocalAi = config.useLocalAi || false;
if (this.useLocalAi && config.selectedModelId) {
const modelsPath = path.join(__dirname, '..', '..', 'config', 'local_models.json');
if (fs.existsSync(modelsPath)) {
const modelsData = fs.readFileSync(modelsPath, 'utf8');
const models = JSON.parse(modelsData);
this.localModel = models.find(m => m.id === config.selectedModelId);
if (!this.localModel) {
console.warn(`[AI分析] 配置的本地模型 ${config.selectedModelId} 不存在回退到云端AI`);
this.useLocalAi = false;
}
}
}
}
} catch (error) {
console.error('[AI分析] 加载本地AI配置失败:', error);
this.useLocalAi = false;
}
}
/**
* 分析检查结果
* @param {Array} issues - 检查结果列表
@ -56,13 +93,22 @@ class AIAnalyzer {
}
try {
// 重新加载配置(可能已更新)
this.loadLocalAiConfig();
// 先进行基础去重
const deduplicated = this.basicDeduplication(issues);
// 检查是否有可用的API配置
const hasApiConfig = (this.provider === 'openai' && this.openaiApiKey) ||
(this.provider === 'xf' && this.xfApiPassword);
// 检查是否使用本地AI
if (this.useLocalAi && this.localModel) {
console.log('[AI分析] 使用本地AI模型进行分析');
return await this.aiAnalysis(deduplicated.issues, projectPath);
}
// 检查是否有可用的云端API配置
const hasApiConfig = (this.provider === 'openai' && this.openaiApiKey) ||
(this.provider === 'xf' && this.xfApiPassword);
if (hasApiConfig) {
return await this.aiAnalysis(deduplicated.issues, projectPath);
} else {
@ -87,7 +133,7 @@ class AIAnalyzer {
for (const issue of issues) {
// 生成唯一键:文件路径 + 行号 + 规则ID
const key = `${issue.relative_path || issue.file}:${issue.line}:${issue.rule || 'unknown'}`;
if (seen.has(key)) {
// 检查消息相似度
const existing = seen.get(key);
@ -95,7 +141,7 @@ class AIAnalyzer {
issue.message || '',
existing.message || ''
);
if (similarity < 0.7) {
// 消息差异较大,可能是不同的问题
uniqueIssues.push(issue);
@ -120,7 +166,7 @@ class AIAnalyzer {
issues: uniqueIssues,
total_issues: uniqueIssues.length,
duplicates_removed: duplicates.length,
deduplication_rate: issues.length > 0
deduplication_rate: issues.length > 0
? ((duplicates.length / issues.length) * 100).toFixed(2) + '%'
: '0%'
};
@ -145,7 +191,7 @@ class AIAnalyzer {
const analyzed = issues.map(issue => {
const risk = this.assessRisk(issue);
const suggestion = this.generateSuggestion(issue);
return {
...issue,
risk_level: risk.level,
@ -175,10 +221,17 @@ class AIAnalyzer {
try {
// 一次性发送所有问题,不再分批处理
console.log(`[AI分析] 开始AI分析${issues.length} 个问题,一次性处理`);
const analyzedIssues = this.provider === 'xf'
? await this.analyzeBatchXf(issues, projectPath)
: await this.analyzeBatch(issues, projectPath);
let analyzedIssues;
// 根据配置选择AI服务
if (this.useLocalAi && this.localModel) {
analyzedIssues = await this.analyzeBatchLocal(issues, projectPath);
} else if (this.provider === 'xf') {
analyzedIssues = await this.analyzeBatchXf(issues, projectPath);
} else {
analyzedIssues = await this.analyzeBatch(issues, projectPath);
}
// 按风险评分排序
analyzedIssues.sort((a, b) => b.risk_score - a.risk_score);
@ -189,7 +242,7 @@ class AIAnalyzer {
high_risk_count: analyzedIssues.filter(i => i.risk_level === 'high').length,
medium_risk_count: analyzedIssues.filter(i => i.risk_level === 'medium').length,
low_risk_count: analyzedIssues.filter(i => i.risk_level === 'low').length,
analysis_method: 'ai-powered'
analysis_method: this.useLocalAi ? 'local-ai' : 'ai-powered'
};
} catch (error) {
console.error('[AI分析] AI分析失败:', error);
@ -203,7 +256,7 @@ class AIAnalyzer {
*/
async analyzeBatch(issues, projectPath) {
const prompt = await this.buildAnalysisPrompt(issues, projectPath);
try {
const requestData = JSON.stringify({
model: this.openaiModel,
@ -270,7 +323,7 @@ class AIAnalyzer {
console.log(`[AI分析-OpenAI] 收到响应,长度: ${aiContent.length} 字符`);
console.log(`[AI分析-OpenAI] 响应内容预览: ${aiContent.substring(0, 300)}`);
// 解析AI响应中的JSON
let analysisResult;
try {
@ -295,21 +348,21 @@ class AIAnalyzer {
throw new Error('AI响应中未找到有效的JSON格式');
}
}
// 验证分析结果格式
if (!analysisResult.issues || !Array.isArray(analysisResult.issues)) {
console.warn('[AI分析-OpenAI] AI返回的格式不正确使用规则基础分析');
console.warn('[AI分析-OpenAI] 返回的数据结构:', JSON.stringify(analysisResult).substring(0, 500));
throw new Error('AI返回格式不正确');
}
console.log(`[AI分析-OpenAI] 成功解析,包含 ${analysisResult.issues.length} 个问题的分析结果`);
// 合并分析结果到原始问题
return issues.map((issue, index) => {
const analysis = analysisResult.issues && analysisResult.issues[index] ? analysisResult.issues[index] : {};
const aiSuggestion = analysis.suggestion || '';
// 验证AI返回的建议是否有效
if (!aiSuggestion || aiSuggestion.trim().length < 20) {
console.warn(`[AI分析-OpenAI] 问题 ${index + 1} 的AI建议过短或为空使用规则基础建议`);
@ -317,7 +370,7 @@ class AIAnalyzer {
} else {
console.log(`[AI分析-OpenAI] 问题 ${index + 1} 获得AI建议长度: ${aiSuggestion.length} 字符`);
}
return {
...issue,
risk_level: analysis.risk_level || this.assessRisk(issue).level,
@ -349,7 +402,7 @@ class AIAnalyzer {
*/
async analyzeBatchXf(issues, projectPath) {
const prompt = await this.buildAnalysisPrompt(issues, projectPath);
try {
// 构建请求数据 - 使用类似OpenAI的格式
const requestData = JSON.stringify({
@ -370,13 +423,13 @@ class AIAnalyzer {
// 解析API URL
const url = new URL(this.xfApiUrl);
// 根据官方文档HTTP API使用Bearer Token认证APIpassword
// 文档https://www.xfyun.cn/doc/spark/X1http.html
if (!this.xfApiPassword) {
throw new Error('未配置XF_API_PASSWORDHTTP协议的APIpassword请在控制台获取https://console.xfyun.cn/services/bmx1');
}
const options = {
hostname: url.hostname,
port: url.port || 443,
@ -438,12 +491,12 @@ class AIAnalyzer {
console.log(`[AI分析-讯飞] 收到响应,长度: ${aiContent.length} 字符`);
console.log(`[AI分析-讯飞] 响应内容预览: ${aiContent.substring(0, 500)}`);
// 如果响应太长,也打印末尾部分
if (aiContent.length > 1000) {
console.log(`[AI分析-讯飞] 响应内容末尾: ...${aiContent.substring(aiContent.length - 500)}`);
}
// 解析AI响应中的JSON
let analysisResult;
try {
@ -453,7 +506,7 @@ class AIAnalyzer {
} catch (e) {
console.log('[AI分析-讯飞] 直接解析失败尝试提取JSON部分');
console.log('[AI分析-讯飞] 错误信息:', e.message);
// 方法1: 尝试提取markdown代码块中的JSON
let jsonText = '';
const codeBlockMatch = aiContent.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
@ -468,7 +521,7 @@ class AIAnalyzer {
console.log('[AI分析-讯飞] 从响应中提取JSON对象');
}
}
if (jsonText) {
try {
// 尝试清理JSON文本移除可能的控制字符
@ -478,44 +531,44 @@ class AIAnalyzer {
} catch (e2) {
console.error('[AI分析-讯飞] 提取的JSON解析失败:', e2.message);
console.error('[AI分析-讯飞] 错误位置:', e2.message.match(/position (\d+)/)?.[1] || '未知');
// 尝试修复常见的JSON错误
try {
// 先尝试找到JSON的起始和结束位置
let startPos = jsonText.indexOf('{');
let endPos = jsonText.lastIndexOf('}');
if (startPos >= 0 && endPos > startPos) {
jsonText = jsonText.substring(startPos, endPos + 1);
}
// 尝试修复未转义的引号(在字符串值中)
// 使用更智能的方法识别JSON结构只修复字符串值中的引号
// 策略:找到 "key": "value" 模式修复value中的引号
// 使用状态机方法修复JSON中的未转义引号
// 策略遍历JSON识别字符串值转义其中的未转义引号
let fixedJson = '';
let inString = false;
let escapeNext = false;
for (let i = 0; i < jsonText.length; i++) {
const char = jsonText[i];
if (escapeNext) {
// 当前字符是转义序列的一部分
fixedJson += char;
escapeNext = false;
continue;
}
if (char === '\\') {
// 遇到反斜杠,下一个字符是转义字符
fixedJson += char;
escapeNext = true;
continue;
}
if (char === '"') {
if (!inString) {
// 字符串开始
@ -529,7 +582,7 @@ class AIAnalyzer {
while (j < jsonText.length && /\s/.test(jsonText[j])) {
j++;
}
if (j >= jsonText.length) {
// 到达末尾,这是字符串结束
inString = false;
@ -550,15 +603,15 @@ class AIAnalyzer {
fixedJson += char;
}
}
jsonText = fixedJson;
// 尝试解析修复后的JSON
analysisResult = JSON.parse(jsonText);
console.log('[AI分析-讯飞] 修复后成功解析JSON');
} catch (e3) {
console.error('[AI分析-讯飞] 修复后仍然失败:', e3.message);
// 打印错误位置的上下文
const errorPos = parseInt(e3.message.match(/position (\d+)/)?.[1] || '0');
if (errorPos > 0) {
@ -567,12 +620,12 @@ class AIAnalyzer {
console.error('[AI分析-讯飞] 错误位置上下文:', jsonText.substring(start, end));
console.error('[AI分析-讯飞] 错误位置标记:', ' '.repeat(Math.min(100, errorPos - start)) + '^');
}
console.error('[AI分析-讯飞] 尝试解析的JSON文本前1000字符:', jsonText.substring(0, 1000));
if (jsonText.length > 1000) {
console.error('[AI分析-讯飞] 尝试解析的JSON文本后1000字符:', jsonText.substring(Math.max(0, jsonText.length - 1000)));
}
// 最后尝试使用eval不安全但作为最后的尝试
// 实际上我们应该避免使用eval而是抛出错误让用户知道
throw new Error(`无法解析AI响应为JSON: ${e2.message}。错误位置: ${errorPos}。请检查AI返回的JSON格式是否正确。`);
@ -584,34 +637,34 @@ class AIAnalyzer {
throw new Error('AI响应中未找到有效的JSON格式');
}
}
// 验证分析结果格式
if (!analysisResult.issues || !Array.isArray(analysisResult.issues)) {
console.warn('[AI分析-讯飞] AI返回的格式不正确使用规则基础分析');
console.warn('[AI分析-讯飞] 返回的数据结构:', JSON.stringify(analysisResult).substring(0, 500));
throw new Error('AI返回格式不正确');
}
console.log(`[AI分析-讯飞] 成功解析,包含 ${analysisResult.issues.length} 个问题的分析结果`);
console.log(`[AI分析-讯飞] 原始问题数量: ${issues.length}`);
// 检查返回的issues数组长度
if (analysisResult.issues.length < issues.length) {
console.warn(`[AI分析-讯飞] AI返回的issues数组长度不足: ${analysisResult.issues.length} < ${issues.length}`);
console.warn(`[AI分析-讯飞] 将使用规则基础分析补充缺失的问题`);
// 打印AI返回的每个问题的建议长度用于调试
analysisResult.issues.forEach((item, idx) => {
const suggestion = item.suggestion || '';
console.log(`[AI分析-讯飞] 问题 ${idx + 1}: suggestion长度=${suggestion.length}, risk_level=${item.risk_level}, risk_score=${item.risk_score}`);
});
}
// 合并分析结果到原始问题
return issues.map((issue, index) => {
const analysis = analysisResult.issues && analysisResult.issues[index] ? analysisResult.issues[index] : {};
const aiSuggestion = analysis.suggestion || '';
// 如果AI返回的数组长度不够或者建议为空/过短,使用规则基础建议
if (index >= analysisResult.issues.length) {
console.warn(`[AI分析-讯飞] 问题 ${index + 1} 超出AI返回范围使用规则基础建议`);
@ -625,7 +678,7 @@ class AIAnalyzer {
ai_analyzed: false
};
}
// 验证AI返回的建议是否有效
if (!aiSuggestion || aiSuggestion.trim().length < 20) {
console.warn(`[AI分析-讯飞] 问题 ${index + 1} 的AI建议过短或为空使用规则基础建议`);
@ -667,6 +720,362 @@ class AIAnalyzer {
}
}
/**
* 使用本地AI模型批量分析问题
*/
async analyzeBatchLocal(issues, projectPath) {
const prompt = await this.buildAnalysisPrompt(issues, projectPath);
try {
if (!this.localModel) {
throw new Error('本地模型未配置');
}
const requestData = JSON.stringify({
model: this.localModel.name,
messages: [
{
role: 'system',
content: '你是一个专业的Python代码质量分析专家擅长分析代码问题评估风险并提供详细、具体的修改建议。你的建议必须包含问题分析、修复步骤、代码示例和最佳实践。'
},
{
role: 'user',
content: prompt
}
],
temperature: 0.3,
max_tokens: 8000
});
const apiUrl = new URL(this.localModel.apiUrl);
const http = require('http');
const https = require('https');
const protocol = apiUrl.protocol === 'https:' ? https : http;
// 确保使用正确的OpenAI兼容端点
let apiPath = apiUrl.pathname;
if (!apiPath.endsWith('/chat/completions')) {
// 如果路径是 /v1自动添加 /chat/completions
if (apiPath.endsWith('/v1') || apiPath === '/v1') {
apiPath = '/v1/chat/completions';
} else if (apiPath.endsWith('/v1/')) {
apiPath = '/v1/chat/completions';
} else if (!apiPath || apiPath === '/') {
apiPath = '/v1/chat/completions';
}
}
const options = {
hostname: apiUrl.hostname,
port: apiUrl.port || (apiUrl.protocol === 'https:' ? 443 : 80),
path: apiPath + (apiUrl.search || ''),
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(requestData)
},
timeout: 120000 // 本地模型可能需要更长时间
};
console.log(`[AI分析-本地] 发送HTTP请求到: ${this.localModel.apiUrl}`);
console.log(`[AI分析-本地] 使用模型: ${this.localModel.name}`);
const data = await new Promise((resolve, reject) => {
const req = protocol.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
if (res.statusCode !== 200) {
console.error(`[AI分析-本地] HTTP错误: ${res.statusCode} ${res.statusMessage}`);
console.error(`[AI分析-本地] 响应内容:`, responseData.substring(0, 500));
reject(new Error(`本地AI API错误: ${res.statusCode} ${res.statusMessage}`));
return;
}
try {
resolve(JSON.parse(responseData));
} catch (e) {
console.error('[AI分析-本地] 解析响应失败:', e);
console.error('[AI分析-本地] 响应内容:', responseData.substring(0, 500));
reject(new Error(`解析AI响应失败: ${e.message}`));
}
});
});
req.on('error', (error) => {
console.error('[AI分析-本地] 请求错误:', error);
reject(error);
});
req.on('timeout', () => {
req.destroy();
reject(new Error('请求超时'));
});
req.write(requestData);
req.end();
});
let aiContent = '';
if (data.choices && data.choices.length > 0) {
aiContent = data.choices[0].message.content || '';
} else if (data.content) {
aiContent = data.content;
} else {
throw new Error('AI响应中未找到内容');
}
console.log(`[AI分析-本地] 收到响应,长度: ${aiContent.length} 字符`);
console.log(`[AI分析-本地] 响应内容预览: ${aiContent.substring(0, 1000)}`);
// 解析JSON响应增强的解析逻辑
let analysisResult;
try {
analysisResult = JSON.parse(aiContent);
console.log('[AI分析-本地] 直接解析JSON成功');
} catch (e) {
console.log('[AI分析-本地] 直接解析失败尝试提取并修复JSON部分');
let jsonText = '';
// 尝试从markdown代码块中提取
const codeBlockMatch = aiContent.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
if (codeBlockMatch) {
jsonText = codeBlockMatch[1];
console.log('[AI分析-本地] 从代码块中提取JSON');
} else {
// 尝试提取第一个完整的JSON对象
const jsonMatch = aiContent.match(/\{[\s\S]*\}/);
if (jsonMatch) {
jsonText = jsonMatch[0];
console.log('[AI分析-本地] 从文本中提取JSON');
}
}
if (jsonText) {
try {
// 清理控制字符
jsonText = jsonText.replace(/[\x00-\x1F\x7F]/g, '');
// 尝试修复常见的JSON格式错误
// 1. 修复 "issues": 后面缺少数组括号的情况
jsonText = jsonText.replace(/"issues"\s*:\s*([^{])/g, '"issues": [$1');
// 2. 修复未闭合的字符串
jsonText = jsonText.replace(/("suggestion"\s*:\s*"[^"]*?)(\n|$)/g, '$1"');
// 3. 确保 issues 是数组
if (jsonText.includes('"issues"') && !jsonText.includes('"issues"[') && !jsonText.includes('"issues": [')) {
jsonText = jsonText.replace(/"issues"\s*:\s*\{/g, '"issues": [{');
// 如果最后一个issue对象后面没有闭合添加闭合
if (!jsonText.includes(']')) {
jsonText = jsonText.replace(/(\})\s*$/, '$1]');
}
}
// 4. 修复未转义的换行符
jsonText = jsonText.replace(/([^\\])\n/g, '$1\\n');
// 5. 尝试修复未闭合的对象
const openBraces = (jsonText.match(/\{/g) || []).length;
const closeBraces = (jsonText.match(/\}/g) || []).length;
if (openBraces > closeBraces) {
jsonText += '}'.repeat(openBraces - closeBraces);
}
console.log('[AI分析-本地] 修复后的JSON预览:', jsonText.substring(0, 500));
analysisResult = JSON.parse(jsonText);
console.log('[AI分析-本地] 成功提取并解析JSON');
} catch (e2) {
console.error('[AI分析-本地] 提取的JSON解析失败:', e2.message);
console.error('[AI分析-本地] 错误位置:', e2.message.match(/position (\d+)/)?.[1] || '未知');
console.error('[AI分析-本地] 错误位置上下文:', jsonText.substring(Math.max(0, parseInt(e2.message.match(/position (\d+)/)?.[1] || '0') - 50), parseInt(e2.message.match(/position (\d+)/)?.[1] || '0') + 50));
// 如果修复失败,尝试手动解析部分数据
console.log('[AI分析-本地] 尝试手动解析部分数据...');
analysisResult = this.parsePartialJSON(jsonText, issues.length);
}
} else {
throw new Error('AI响应中未找到有效的JSON格式');
}
}
if (!analysisResult || !analysisResult.issues || !Array.isArray(analysisResult.issues)) {
console.warn('[AI分析-本地] AI返回的格式不正确issues不是数组');
console.warn('[AI分析-本地] analysisResult类型:', typeof analysisResult);
if (analysisResult) {
console.warn('[AI分析-本地] analysisResult keys:', Object.keys(analysisResult));
console.warn('[AI分析-本地] analysisResult.issues类型:', typeof analysisResult.issues);
}
// 如果issues存在但不是数组尝试转换
if (analysisResult && analysisResult.issues && typeof analysisResult.issues === 'object' && !Array.isArray(analysisResult.issues)) {
console.log('[AI分析-本地] 尝试将issues对象转换为数组');
analysisResult.issues = Object.values(analysisResult.issues);
}
// 如果仍然不是数组,抛出错误
if (!analysisResult || !Array.isArray(analysisResult.issues)) {
throw new Error('AI返回格式不正确issues不是数组');
}
}
console.log(`[AI分析-本地] 成功解析,包含 ${analysisResult.issues.length} 个问题的分析结果`);
// 检查返回的issues数组长度
if (analysisResult.issues.length < issues.length) {
console.warn(`[AI分析-本地] AI返回的issues数组长度不足: ${analysisResult.issues.length} < ${issues.length}`);
console.warn(`[AI分析-本地] 将为缺失的问题补充空分析结果`);
// 补充缺失的问题
while (analysisResult.issues.length < issues.length) {
analysisResult.issues.push({
risk_level: 'medium',
risk_score: 50,
suggestion: ''
});
}
} else if (analysisResult.issues.length > issues.length) {
console.warn(`[AI分析-本地] AI返回的issues数组长度超出: ${analysisResult.issues.length} > ${issues.length}`);
console.warn(`[AI分析-本地] 将截取前 ${issues.length} 个结果`);
analysisResult.issues = analysisResult.issues.slice(0, issues.length);
}
return issues.map((issue, index) => {
const analysis = analysisResult.issues && analysisResult.issues[index] ? analysisResult.issues[index] : {};
const aiSuggestion = (analysis.suggestion || '').trim();
// 如果AI返回了建议即使较短优先使用AI的建议
if (index < analysisResult.issues.length && aiSuggestion && aiSuggestion.length > 0) {
// 如果建议太短,补充一些基础信息
let finalSuggestion = aiSuggestion;
if (aiSuggestion.length < 30) {
const risk = this.assessRisk(issue);
const basicSuggestion = this.generateSuggestion(issue);
finalSuggestion = `${aiSuggestion}\n\n补充建议:${basicSuggestion}`;
console.log(`[AI分析-本地] 问题 ${index + 1} 的AI建议较短(${aiSuggestion.length}字符),已补充基础建议`);
} else {
console.log(`[AI分析-本地] 问题 ${index + 1} 获得AI建议长度: ${aiSuggestion.length} 字符`);
}
return {
...issue,
risk_level: analysis.risk_level || this.assessRisk(issue).level,
risk_score: analysis.risk_score !== undefined ? analysis.risk_score : this.assessRisk(issue).score,
suggestion: finalSuggestion,
ai_analyzed: true
};
} else {
// 如果AI没有返回建议使用规则基础建议
console.warn(`[AI分析-本地] 问题 ${index + 1} 的AI建议为空或缺失使用规则基础建议`);
const risk = this.assessRisk(issue);
const suggestion = this.generateSuggestion(issue);
return {
...issue,
risk_level: analysis.risk_level || risk.level,
risk_score: analysis.risk_score !== undefined ? analysis.risk_score : risk.score,
suggestion: suggestion,
ai_analyzed: false
};
}
});
} catch (error) {
console.error('[AI分析-本地] 批量分析失败:', error);
return issues.map(issue => {
const risk = this.assessRisk(issue);
const suggestion = this.generateSuggestion(issue);
return {
...issue,
risk_level: risk.level,
risk_score: risk.score,
suggestion: suggestion,
ai_analyzed: false
};
});
}
}
/**
* 手动解析部分JSON数据当JSON格式不完整时
*/
parsePartialJSON(jsonText, expectedCount) {
console.log('[AI分析-本地] 开始手动解析部分JSON数据');
const parsedIssues = [];
try {
// 尝试提取每个issue对象 - 使用更宽松的正则
const lines = jsonText.split('\n');
let currentIssue = null;
let currentSuggestion = '';
let inSuggestion = false;
for (let i = 0; i < lines.length && parsedIssues.length < expectedCount; i++) {
const line = lines[i].trim();
// 匹配 risk_level
const riskLevelMatch = line.match(/"risk_level"\s*:\s*"([^"]+)"/);
if (riskLevelMatch) {
if (currentIssue && currentIssue.suggestion) {
parsedIssues.push(currentIssue);
}
currentIssue = {
risk_level: riskLevelMatch[1],
risk_score: 50,
suggestion: ''
};
inSuggestion = false;
currentSuggestion = '';
continue;
}
// 匹配 risk_score
const riskScoreMatch = line.match(/"risk_score"\s*:\s*(\d+)/);
if (riskScoreMatch && currentIssue) {
currentIssue.risk_score = parseInt(riskScoreMatch[1]);
continue;
}
// 匹配 suggestion 开始
const suggestionStartMatch = line.match(/"suggestion"\s*:\s*"([^"]*)/);
if (suggestionStartMatch) {
currentSuggestion = suggestionStartMatch[1];
inSuggestion = true;
// 检查是否在同一行结束
if (line.endsWith('"') && !line.endsWith('\\"')) {
if (currentIssue) {
currentIssue.suggestion = currentSuggestion;
}
currentSuggestion = '';
inSuggestion = false;
}
continue;
}
// 如果在suggestion中继续收集
if (inSuggestion && currentIssue) {
// 检查是否结束
if (line.endsWith('"') && !line.endsWith('\\"')) {
currentSuggestion += ' ' + line.slice(0, -1);
currentIssue.suggestion = currentSuggestion;
currentSuggestion = '';
inSuggestion = false;
} else {
currentSuggestion += ' ' + line;
}
}
}
// 添加最后一个issue
if (currentIssue && currentIssue.suggestion) {
parsedIssues.push(currentIssue);
}
console.log(`[AI分析-本地] 手动解析出 ${parsedIssues.length} 个问题`);
return { issues: parsedIssues };
} catch (error) {
console.error('[AI分析-本地] 手动解析也失败:', error);
// 返回空数组,让系统使用规则基础分析
return { issues: [] };
}
}
/**
* 构建AI分析提示词
@ -709,12 +1118,12 @@ class AIAnalyzer {
// 构建问题列表(包含完整文件内容)
let issuesText = '';
let issueIndex = 1;
for (const [filePath, fileData] of filesData.entries()) {
issuesText += `\n## 文件: ${filePath}\n`;
issuesText += `完整代码(共 ${fileData.lineCount} 行):\n\`\`\`python\n${fileData.content}\n\`\`\`\n\n`;
issuesText += `该文件中的问题:\n`;
for (const issue of fileData.issues) {
issuesText += `${issueIndex}. 行号: ${issue.line}, 列号: ${issue.column || 0}\n`;
issuesText += ` 规则: ${issue.rule || '未知'}\n`;
@ -788,11 +1197,12 @@ ${issuesText}
]
}
**重要提示**
**重要提示最后提醒**
- 只返回JSON对象不要包含任何解释文字
- 不要使用markdown代码块包裹JSON
- JSON字符串中的换行符引号等特殊字符必须正确转义
- 必须返回 ${issues.length} 个issues每个都必须有完整的suggestion**`;
- **必须返回 ${issues.length} 个issues每个都必须有完整的suggestion**
- **这是硬性要求不能违反**如果返回的issues数量不是 ${issues.length}系统将无法正常工作**`;
}
/**
@ -816,8 +1226,8 @@ ${issuesText}
const securityKeywords = ['security', 'injection', 'xss', 'csrf', 'sql', 'password', 'secret', 'key', 'token'];
const ruleLower = (issue.rule || '').toLowerCase();
const messageLower = (issue.message || '').toLowerCase();
if (securityKeywords.some(keyword =>
if (securityKeywords.some(keyword =>
ruleLower.includes(keyword) || messageLower.includes(keyword)
)) {
score += 30;
@ -844,28 +1254,28 @@ ${issuesText}
if (ruleLower.includes('import') || messageLower.includes('import')) {
return '检查导入语句确保导入的模块存在且路径正确。考虑使用相对导入或检查PYTHONPATH设置。';
}
if (ruleLower.includes('unused') || messageLower.includes('unused')) {
return '删除未使用的变量、函数或导入。如果确实需要保留,可以在变量名前加下划线(如 _unused_var。';
}
if (ruleLower.includes('naming') || messageLower.includes('naming')) {
return '遵循PEP 8命名规范类名使用驼峰命名CamelCase函数和变量使用下划线命名snake_case。';
}
if (ruleLower.includes('security') || messageLower.includes('security') ||
if (ruleLower.includes('security') || messageLower.includes('security') ||
ruleLower.includes('bandit')) {
return '这是一个安全问题。请仔细检查代码,确保没有安全漏洞。考虑使用安全的替代方案,如使用参数化查询而不是字符串拼接。';
}
if (ruleLower.includes('complexity') || messageLower.includes('complexity')) {
return '代码复杂度过高。考虑将函数拆分为更小的函数,提高代码可读性和可维护性。';
}
if (messageLower.includes('line too long')) {
return '行长度超过限制。将长行拆分为多行,或使用括号、反斜杠进行换行。';
}
if (messageLower.includes('missing docstring')) {
return '添加函数或类的文档字符串docstring说明其功能、参数和返回值。';
}

@ -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()

@ -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的灵活切换特别针对军工等特殊领域提供定制化的代码合规性检查方案以提升代码质量、提高开发效率、降低安全风险为代码质量检查领域提供新的理论支撑和实践价值。

@ -1,424 +0,0 @@
# 配置中心设计文档
## 概述
配置中心用于集中管理系统中的所有配置项支持通过环境变量、配置文件或Web界面进行配置管理。
---
## 一、服务器配置
### 1.1 基础服务配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 服务端口 | `PORT` | `5000` | 后端服务器监听端口 |
| 主机地址 | `HOST` | `0.0.0.0` | 服务器绑定地址0.0.0.0表示所有接口) |
| 环境模式 | `NODE_ENV` | `development` | 运行环境development/production/test |
| API前缀 | `API_PREFIX` | `/api` | API接口统一前缀 |
### 1.2 CORS跨域配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 允许来源 | `CORS_ORIGIN` | `*` | 允许的跨域来源(生产环境建议设置为具体域名) |
| 允许方法 | `CORS_METHODS` | `GET,POST,PUT,DELETE,OPTIONS` | 允许的HTTP方法 |
| 允许请求头 | `CORS_HEADERS` | `Content-Type,Authorization` | 允许的请求头 |
| 允许凭证 | `CORS_CREDENTIALS` | `true` | 是否允许携带凭证 |
### 1.3 请求体限制
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| JSON大小限制 | `JSON_LIMIT` | `10mb` | JSON请求体大小限制 |
| URL编码限制 | `URL_ENCODED_LIMIT` | `10mb` | URL编码请求体大小限制 |
---
## 二、AI分析服务配置
### 2.1 服务开关
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| AI分析启用 | `AI_ANALYZER_ENABLED` | `true` | 是否启用AI分析功能 |
| AI提供商 | `AI_PROVIDER` | `xf` | 使用的AI服务`openai` 或 `xf` |
### 2.2 OpenAI配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| API密钥 | `OPENAI_API_KEY` | - | OpenAI API密钥必需 |
| API基础URL | `OPENAI_API_BASE` | `https://api.openai.com/v1` | OpenAI API基础地址 |
| 模型名称 | `OPENAI_MODEL` | `gpt-3.5-turbo` | 使用的模型gpt-3.5-turbo, gpt-4 |
| 最大Token数 | `OPENAI_MAX_TOKENS` | `8000` | 单次请求最大Token数 |
| 温度参数 | `OPENAI_TEMPERATURE` | `0.7` | 生成文本的随机性0-1 |
| 请求超时 | `OPENAI_TIMEOUT` | `60000` | API请求超时时间毫秒 |
### 2.3 科大讯飞iFlytek配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| API密码 | `XF_API_PASSWORD` | - | HTTP API的APIpasswordBearer Token |
| API密钥 | `XF_API_KEY` | - | WebSocket API的API Key兼容旧配置 |
| API密钥 | `XF_API_SECRET` | - | WebSocket API的API Secret兼容旧配置 |
| 模型名称 | `XF_MODEL` | `spark-x` | 使用的模型spark-x表示Spark X1.5 |
| API地址 | `XF_API_URL` | `https://spark-api-open.xf-yun.com/v2/chat/completions` | HTTP API地址 |
| 最大Token数 | `XF_MAX_TOKENS` | `8000` | 单次请求最大Token数 |
| 请求超时 | `XF_TIMEOUT` | `60000` | API请求超时时间毫秒 |
### 2.4 AI分析参数
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 批量大小 | `AI_BATCH_SIZE` | `50` | 单次AI分析的问题数量 |
| 最小建议长度 | `AI_MIN_SUGGESTION_LENGTH` | `20` | AI建议的最小字符数低于此值将使用规则建议 |
| 去重相似度阈值 | `AI_DEDUP_THRESHOLD` | `0.8` | 问题去重的相似度阈值0-1 |
---
## 三、代码检查工具配置
### 3.1 工具启用配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| Pylint启用 | `TOOL_PYLINT_ENABLED` | `true` | 是否启用Pylint检查 |
| Flake8启用 | `TOOL_FLAKE8_ENABLED` | `true` | 是否启用Flake8检查 |
| Bandit启用 | `TOOL_BANDIT_ENABLED` | `true` | 是否启用Bandit检查 |
### 3.2 工具执行配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 命令超时 | `TOOL_TIMEOUT` | `60000` | 单个工具执行超时时间(毫秒) |
| 并发检查数 | `TOOL_CONCURRENT` | `3` | 同时执行的工具数量 |
| Python路径 | `PYTHON_PATH` | `python` | Python解释器路径 |
### 3.3 Pylint配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| Pylint命令 | `PYLINT_CMD` | `python -m pylint` | Pylint执行命令 |
| 默认配置文件 | `PYLINT_DEFAULT_RC` | - | 默认的pylintrc配置文件路径 |
| 输出格式 | `PYLINT_FORMAT` | `json` | 输出格式json/text |
### 3.4 Flake8配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| Flake8命令 | `FLAKE8_CMD` | `python -m flake8` | Flake8执行命令 |
| 默认配置文件 | `FLAKE8_DEFAULT_CONFIG` | - | 默认的setup.cfg或tox.ini配置文件路径 |
| 输出格式 | `FLAKE8_FORMAT` | `default` | 输出格式 |
### 3.5 Bandit配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| Bandit命令 | `BANDIT_CMD` | `python -m bandit` | Bandit执行命令 |
| 默认配置文件 | `BANDIT_DEFAULT_CONFIG` | - | 默认的bandit配置文件路径 |
| 输出格式 | `BANDIT_FORMAT` | `json` | 输出格式json/txt/csv |
| 安全级别 | `BANDIT_LEVEL` | `1` | 最低报告的安全级别1-3 |
---
## 四、文件上传配置
### 4.1 上传限制
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 单文件大小限制 | `UPLOAD_MAX_FILE_SIZE` | `100MB` | 单个文件最大大小 |
| 总大小限制 | `UPLOAD_MAX_TOTAL_SIZE` | `500MB` | 单次上传总大小限制 |
| 文件数量限制 | `UPLOAD_MAX_FILES` | `1000` | 单次上传最大文件数 |
| 允许的文件类型 | `UPLOAD_ALLOWED_EXT` | `.py,.pyx,.pyi` | 允许的文件扩展名(逗号分隔) |
### 4.2 存储配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 临时目录 | `UPLOAD_TEMP_DIR` | `os.tmpdir()/fortifycode_uploads` | 文件上传临时存储目录 |
| 自动清理 | `UPLOAD_AUTO_CLEANUP` | `true` | 是否自动清理临时文件 |
| 清理间隔 | `UPLOAD_CLEANUP_INTERVAL` | `3600000` | 临时文件清理间隔毫秒1小时 |
| 文件保留时间 | `UPLOAD_RETENTION_HOURS` | `24` | 临时文件保留时间(小时) |
---
## 五、存储路径配置
### 5.1 目录配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 项目数据目录 | `PROJECTS_DIR` | `./projects_data` | 项目数据存储根目录 |
| 规则集目录 | `RULE_DIR` | `./rule` | 规则集配置文件存储目录 |
| 报告输出目录 | `OUT_DIR` | `./out` | 检查报告输出目录 |
| 日志目录 | `LOG_DIR` | `./logs` | 日志文件存储目录 |
### 5.2 数据持久化
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 数据存储方式 | `DATA_STORAGE_TYPE` | `file` | 存储方式:`file`(文件)或 `database`(数据库) |
| 数据库连接 | `DATABASE_URL` | - | 数据库连接字符串(当使用数据库时) |
| 自动备份 | `DATA_AUTO_BACKUP` | `true` | 是否自动备份数据 |
| 备份间隔 | `DATA_BACKUP_INTERVAL` | `86400000` | 数据备份间隔毫秒24小时 |
---
## 六、日志配置
### 6.1 日志级别
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 日志级别 | `LOG_LEVEL` | `info` | 日志级别debug/info/warn/error |
| 控制台日志 | `LOG_CONSOLE` | `true` | 是否输出到控制台 |
| 文件日志 | `LOG_FILE` | `true` | 是否输出到文件 |
| 日志文件路径 | `LOG_FILE_PATH` | `./logs/app.log` | 日志文件路径 |
### 6.2 日志格式
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 日志格式 | `LOG_FORMAT` | `json` | 日志格式json/text |
| 时间格式 | `LOG_TIME_FORMAT` | `ISO8601` | 时间格式 |
| 包含堆栈 | `LOG_INCLUDE_STACK` | `false` | 错误日志是否包含堆栈信息 |
### 6.3 日志轮转
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 日志轮转 | `LOG_ROTATE` | `true` | 是否启用日志轮转 |
| 最大文件大小 | `LOG_MAX_SIZE` | `10MB` | 单个日志文件最大大小 |
| 保留文件数 | `LOG_MAX_FILES` | `10` | 保留的日志文件数量 |
---
## 七、安全配置
### 7.1 认证授权
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 启用认证 | `AUTH_ENABLED` | `false` | 是否启用用户认证(生产环境建议启用) |
| JWT密钥 | `JWT_SECRET` | - | JWT令牌签名密钥 |
| Token过期时间 | `JWT_EXPIRES_IN` | `24h` | Token过期时间 |
| 密码加密算法 | `PASSWORD_HASH_ALGO` | `bcrypt` | 密码加密算法 |
### 7.2 API安全
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| API限流 | `API_RATE_LIMIT` | `false` | 是否启用API限流 |
| 限流窗口 | `API_RATE_WINDOW` | `60000` | 限流时间窗口(毫秒) |
| 最大请求数 | `API_RATE_MAX` | `100` | 时间窗口内最大请求数 |
| 请求验证 | `API_VALIDATE_REQUEST` | `true` | 是否验证请求格式 |
### 7.3 文件安全
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 文件类型验证 | `FILE_TYPE_VALIDATION` | `true` | 是否验证文件类型 |
| 文件内容扫描 | `FILE_CONTENT_SCAN` | `false` | 是否扫描文件内容(防病毒) |
| 路径遍历防护 | `PATH_TRAVERSAL_PROTECTION` | `true` | 是否防护路径遍历攻击 |
---
## 八、性能配置
### 8.1 缓存配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 启用缓存 | `CACHE_ENABLED` | `true` | 是否启用缓存 |
| 缓存类型 | `CACHE_TYPE` | `memory` | 缓存类型memory/redis |
| Redis连接 | `REDIS_URL` | - | Redis连接字符串当使用Redis时 |
| 缓存TTL | `CACHE_TTL` | `3600` | 缓存过期时间(秒) |
### 8.2 并发配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 最大并发检查 | `MAX_CONCURRENT_CHECKS` | `5` | 同时运行的最大检查任务数 |
| 工作线程数 | `WORKER_THREADS` | `4` | 工作线程数量 |
| 任务队列大小 | `TASK_QUEUE_SIZE` | `100` | 任务队列最大大小 |
---
## 九、前端配置
### 9.1 UI配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 主题 | `UI_THEME` | `light` | 界面主题light/dark |
| 语言 | `UI_LANGUAGE` | `zh-CN` | 界面语言zh-CN/en-US |
| 每页显示数 | `UI_ITEMS_PER_PAGE` | `20` | 列表每页显示的项目数 |
| 自动刷新 | `UI_AUTO_REFRESH` | `false` | 是否自动刷新数据 |
| 刷新间隔 | `UI_REFRESH_INTERVAL` | `30000` | 自动刷新间隔(毫秒) |
### 9.2 编辑器配置
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 编辑器主题 | `EDITOR_THEME` | `vs` | 代码编辑器主题 |
| 字体大小 | `EDITOR_FONT_SIZE` | `14` | 编辑器字体大小 |
| 显示行号 | `EDITOR_LINE_NUMBERS` | `true` | 是否显示行号 |
| 自动换行 | `EDITOR_WORD_WRAP` | `false` | 是否自动换行 |
| Tab大小 | `EDITOR_TAB_SIZE` | `4` | Tab键缩进空格数 |
---
## 十、通知配置
### 10.1 邮件通知
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 启用邮件 | `EMAIL_ENABLED` | `false` | 是否启用邮件通知 |
| SMTP服务器 | `SMTP_HOST` | - | SMTP服务器地址 |
| SMTP端口 | `SMTP_PORT` | `587` | SMTP端口 |
| 发件人邮箱 | `SMTP_FROM` | - | 发件人邮箱地址 |
| 发件人密码 | `SMTP_PASSWORD` | - | 发件人邮箱密码 |
| 收件人列表 | `EMAIL_RECIPIENTS` | - | 收件人邮箱列表(逗号分隔) |
### 10.2 Webhook通知
| 配置项 | 环境变量 | 默认值 | 说明 |
|--------|---------|--------|------|
| 启用Webhook | `WEBHOOK_ENABLED` | `false` | 是否启用Webhook通知 |
| Webhook URL | `WEBHOOK_URL` | - | Webhook回调地址 |
| Webhook密钥 | `WEBHOOK_SECRET` | - | Webhook签名密钥 |
---
## 十一、配置管理方式
### 11.1 环境变量(推荐)
在启动脚本或系统环境变量中设置:
```bash
# Windows (start_server.bat)
set PORT=5000
set AI_PROVIDER=xf
set XF_API_PASSWORD=your_api_password
# Linux/Mac (start_server.sh)
export PORT=5000
export AI_PROVIDER=xf
export XF_API_PASSWORD=your_api_password
```
### 11.2 配置文件(.env
创建 `.env` 文件(需要安装 `dotenv` 包):
```env
PORT=5000
AI_PROVIDER=xf
XF_API_PASSWORD=your_api_password
TOOL_TIMEOUT=60000
UPLOAD_MAX_FILE_SIZE=100MB
```
### 11.3 Web配置界面未来功能
- 提供Web界面进行配置管理
- 支持配置的导入/导出
- 配置变更历史记录
- 配置验证和测试
---
## 十二、配置优先级
配置项的优先级(从高到低):
1. **环境变量** - 最高优先级
2. **配置文件(.env** - 中等优先级
3. **代码默认值** - 最低优先级
---
## 十三、配置验证
系统启动时应验证:
1. ✅ 必需的配置项是否存在如AI API密钥
2. ✅ 配置值的格式是否正确(如端口号范围)
3. ✅ 路径配置是否存在且可访问
4. ✅ 外部服务连接是否正常如AI API、数据库
---
## 十四、配置示例
### 14.1 开发环境配置
```env
NODE_ENV=development
PORT=5000
LOG_LEVEL=debug
AI_ANALYZER_ENABLED=true
AI_PROVIDER=xf
XF_API_PASSWORD=your_dev_password
TOOL_TIMEOUT=30000
UPLOAD_MAX_FILE_SIZE=50MB
```
### 14.2 生产环境配置
```env
NODE_ENV=production
PORT=5000
LOG_LEVEL=info
CORS_ORIGIN=https://yourdomain.com
AI_ANALYZER_ENABLED=true
AI_PROVIDER=xf
XF_API_PASSWORD=your_prod_password
TOOL_TIMEOUT=120000
UPLOAD_MAX_FILE_SIZE=200MB
AUTH_ENABLED=true
API_RATE_LIMIT=true
DATA_AUTO_BACKUP=true
```
---
## 十五、配置迁移建议
1. **从硬编码迁移到配置中心**
- 识别所有硬编码的配置值
- 逐步迁移到环境变量或配置文件
- 保持向后兼容
2. **配置文档化**
- 维护配置项说明文档
- 提供配置示例
- 记录配置变更历史
3. **配置安全**
- 敏感配置如API密钥使用环境变量
- 不要将敏感配置提交到版本控制
- 使用 `.gitignore` 排除配置文件
---
## 总结
配置中心应包含以上所有配置项,支持:
- ✅ 环境变量配置
- ✅ 配置文件管理
- ✅ 配置验证
- ✅ 配置优先级
- ✅ 配置文档化
- ✅ 敏感信息保护
通过集中管理配置,可以:
- 提高系统的可维护性
- 便于环境切换(开发/测试/生产)
- 增强系统安全性
- 简化部署流程

@ -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日
Loading…
Cancel
Save