功能完善 #5

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

@ -841,6 +841,154 @@ app.post('/api/projects/:id/check', async (req, res) => {
}
});
// 导出分析报告为Markdown
app.post('/api/projects/:id/export-report', (req, res) => {
try {
const projectId = parseInt(req.params.id);
const project = projects.find(p => p.id === projectId);
if (!project) {
return res.status(404).json({
success: false,
error: '项目不存在'
});
}
const resultData = req.body.resultData;
if (!resultData || !resultData.all_issues) {
return res.status(400).json({
success: false,
error: '缺少检查结果数据'
});
}
// 生成Markdown报告
const markdown = generateMarkdownReport(project, resultData);
// 设置响应头,支持下载
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
const filename = `${project.name}_报告_${timestamp}.md`;
res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
res.send(markdown);
} catch (error) {
console.error('导出报告失败:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// 生成Markdown报告
function generateMarkdownReport(project, resultData) {
const issues = resultData.all_issues || [];
const timestamp = new Date().toLocaleString('zh-CN');
let markdown = `# 代码质量检查报告\n\n`;
markdown += `**项目名称:** ${project.name}\n`;
markdown += `**检查时间:** ${timestamp}\n`;
markdown += `**项目路径:** ${project.path}\n\n`;
// 统计信息
const errorCount = resultData.error_count || issues.filter(i => i.type === 'error').length;
const warningCount = resultData.warning_count || issues.filter(i => i.type === 'warning').length;
const infoCount = resultData.info_count || issues.filter(i => i.type === 'info').length;
const totalCount = resultData.total_issues || issues.length;
markdown += `## 📊 检查结果摘要\n\n`;
markdown += `| 类型 | 数量 |\n`;
markdown += `|------|------|\n`;
markdown += `| ❌ 错误 | ${errorCount} |\n`;
markdown += `| ⚠️ 警告 | ${warningCount} |\n`;
markdown += `| 信息 | ${infoCount} |\n`;
markdown += `| 📋 总计 | ${totalCount} |\n\n`;
// AI分析统计
if (resultData.ai_analyzed) {
const highRiskCount = resultData.high_risk_count || issues.filter(i => i.risk_level === 'high').length;
const mediumRiskCount = resultData.medium_risk_count || issues.filter(i => i.risk_level === 'medium').length;
const lowRiskCount = resultData.low_risk_count || issues.filter(i => i.risk_level === 'low').length;
const duplicatesRemoved = resultData.duplicates_removed || 0;
const analysisMethod = resultData.analysis_method || 'none';
markdown += `## 🤖 AI分析结果\n\n`;
markdown += `**分析方式:** ${analysisMethod === 'ai-powered' ? 'AI增强分析' : '规则基础分析'}\n`;
if (duplicatesRemoved > 0) {
markdown += `**去重数量:** ${duplicatesRemoved} 个重复问题已合并\n`;
}
markdown += `\n### 风险评估\n\n`;
markdown += `| 风险等级 | 数量 |\n`;
markdown += `|----------|------|\n`;
markdown += `| 🔴 高风险 | ${highRiskCount} |\n`;
markdown += `| 🟡 中风险 | ${mediumRiskCount} |\n`;
markdown += `| 🟢 低风险 | ${lowRiskCount} |\n\n`;
}
// 问题详情
markdown += `## 🔍 问题详情\n\n`;
if (issues.length === 0) {
markdown += `✅ **检查完成!未发现任何问题,代码质量良好。**\n`;
} else {
// 按文件分组
const issuesByFile = new Map();
issues.forEach(issue => {
const filePath = issue.relative_path || issue.file || '未知文件';
if (!issuesByFile.has(filePath)) {
issuesByFile.set(filePath, []);
}
issuesByFile.get(filePath).push(issue);
});
let issueIndex = 1;
for (const [filePath, fileIssues] of issuesByFile.entries()) {
markdown += `### 📄 文件: ${filePath}\n\n`;
fileIssues.forEach(issue => {
const riskLevel = issue.risk_level || 'low';
const riskEmoji = riskLevel === 'high' ? '🔴' : riskLevel === 'medium' ? '🟡' : '🟢';
const riskLabel = riskLevel === 'high' ? '高风险' : riskLevel === 'medium' ? '中风险' : '低风险';
markdown += `#### 问题 #${issueIndex}\n\n`;
markdown += `- **位置:** ${filePath}:${issue.line}:${issue.column || 0}\n`;
markdown += `- **类型:** ${issue.type || 'unknown'}\n`;
markdown += `- **规则:** ${issue.rule || '未知规则'}\n`;
markdown += `- **消息:** ${issue.message || '无'}\n`;
markdown += `- **严重程度:** ${issue.severity || 'medium'}\n`;
markdown += `- **检测工具:** ${(issue.detected_by && issue.detected_by.length > 0) ? issue.detected_by.join(', ') : (issue.tool || '未知')}\n`;
if (issue.risk_level) {
markdown += `- **风险等级:** ${riskEmoji} ${riskLabel}\n`;
}
if (issue.risk_score !== undefined) {
markdown += `- **风险评分:** ${issue.risk_score}/100\n`;
}
if (issue.confidence) {
markdown += `- **置信度:** ${issue.confidence}\n`;
}
markdown += `\n`;
if (issue.suggestion) {
markdown += `**💡 修改建议:**\n\n`;
markdown += `${issue.suggestion}\n\n`;
}
markdown += `---\n\n`;
issueIndex++;
});
}
}
markdown += `\n---\n\n`;
markdown += `*报告生成时间: ${timestamp}*\n`;
markdown += `*由代码质量检查系统自动生成*\n`;
return markdown;
}
// 上传文件到项目
app.post('/api/projects/:id/upload-files', upload.array('files'), (req, res) => {
try {

@ -308,11 +308,16 @@
<div class="check-results-panel" id="checkResultsPanel" style="display: none;">
<div class="panel-header">
<h2 class="panel-title">检查结果详情</h2>
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
<div style="display: flex; align-items: center; gap: 10px;">
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
</div>
<button id="downloadReportBtn" class="btn btn-primary" style="margin-left: auto;">
<i class="fas fa-download"></i> 下载报告
</button>
</div>
</div>
<div class="panel-content" id="checkResultsContent">

@ -1194,10 +1194,14 @@ function showErrorMessage(message) {
}, 5000);
}
// 保存当前检查结果数据(用于下载报告)
let currentCheckResultData = null;
// 查看检查结果
function displayCheckResultsPanel(resultData) {
const panel = document.getElementById('checkResultsPanel');
if (!panel) return;
currentCheckResultData = resultData; // 保存结果数据
renderProjectResults(resultData.all_issues || [], resultData);
panel.style.display = 'block';
}
@ -1269,6 +1273,69 @@ if (runCheckBtn) {
});
}
// 下载报告按钮
const downloadReportBtn = document.getElementById('downloadReportBtn');
if (downloadReportBtn) {
downloadReportBtn.addEventListener('click', async () => {
if (!currentProject) {
showErrorMessage('请先选择一个项目');
return;
}
if (!currentCheckResultData) {
showErrorMessage('没有可下载的检查结果,请先运行检查');
return;
}
try {
showLoadingOverlay('正在生成报告...');
const response = await fetch(`${API_BASE_URL}/projects/${currentProject.id}/export-report`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
resultData: currentCheckResultData
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '下载失败');
}
// 获取文件名(从响应头)
const contentDisposition = response.headers.get('Content-Disposition');
let filename = `${currentProject.name}_报告_${new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5)}.md`;
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (filenameMatch && filenameMatch[1]) {
filename = decodeURIComponent(filenameMatch[1].replace(/['"]/g, ''));
}
}
// 获取文件内容并下载
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
hideLoadingOverlay();
showSuccessMessage('报告下载成功');
} catch (error) {
hideLoadingOverlay();
console.error('下载报告失败:', error);
showErrorMessage('下载报告失败: ' + error.message);
}
});
}
// 显示规则集选择对话框
let pendingCheckProjectId = null;
async function showRuleSetSelectDialog(projectId) {

@ -17,10 +17,12 @@ class AIAnalyzer {
this.openaiModel = process.env.OPENAI_MODEL || 'gpt-3.5-turbo';
// 科大讯飞配置HTTP API
this.xfAppId = process.env.XF_APP_ID || '96c72c9e';
this.xfApiKey = process.env.XF_API_KEY || 'mfxzmIRlAymtEmsgVojM';
this.xfApiSecret = process.env.XF_API_SECRET || 'ZZIvBMLpPhAHSeATcoDY';
this.xfModel = process.env.XF_MODEL || 'x1'; // Spark X1.5 模型
// 注意HTTP API使用APIpasswordBearer Token不是apikey和apisecret
// 获取地址https://console.xfyun.cn/services/bmx1
this.xfApiPassword = process.env.XF_API_PASSWORD || process.env.XF_API_KEY || ''; // HTTP协议的APIpassword
this.xfApiKey = process.env.XF_API_KEY || ''; // 兼容旧配置
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提供商
@ -29,7 +31,13 @@ class AIAnalyzer {
console.log(`[AI分析] 使用提供商: ${this.provider}`);
if (this.provider === 'xf') {
console.log(`[AI分析] 科大讯飞 APPID: ${this.xfAppId}`);
if (this.xfApiPassword) {
console.log(`[AI分析] 科大讯飞 HTTP API Password: ${this.xfApiPassword.substring(0, 10)}... (已配置)`);
console.log(`[AI分析] 使用模型: ${this.xfModel}`);
} else {
console.log(`[AI分析] 科大讯飞 HTTP API Password: 未配置`);
console.log(`[AI分析] 请在控制台获取APIpassword: https://console.xfyun.cn/services/bmx1`);
}
} else if (this.provider === 'openai') {
console.log(`[AI分析] OpenAI API Key: ${this.openaiApiKey ? '已配置' : '未配置'}`);
}
@ -53,7 +61,7 @@ class AIAnalyzer {
// 检查是否有可用的API配置
const hasApiConfig = (this.provider === 'openai' && this.openaiApiKey) ||
(this.provider === 'xf' && this.xfApiKey && this.xfApiSecret);
(this.provider === 'xf' && this.xfApiPassword);
if (hasApiConfig) {
return await this.aiAnalysis(deduplicated.issues, projectPath);
@ -165,20 +173,12 @@ class AIAnalyzer {
*/
async aiAnalysis(issues, projectPath) {
try {
// 将问题分组,每批处理一定数量
const batchSize = this.provider === 'xf' ? 5 : 10; // 科大讯飞建议小批次
const batches = [];
for (let i = 0; i < issues.length; i += batchSize) {
batches.push(issues.slice(i, i + batchSize));
}
const analyzedIssues = [];
for (const batch of batches) {
const analyzed = this.provider === 'xf'
? await this.analyzeBatchXf(batch, projectPath)
: await this.analyzeBatch(batch, projectPath);
analyzedIssues.push(...analyzed);
}
// 一次性发送所有问题,不再分批处理
console.log(`[AI分析] 开始AI分析${issues.length} 个问题,一次性处理`);
const analyzedIssues = this.provider === 'xf'
? await this.analyzeBatchXf(issues, projectPath)
: await this.analyzeBatch(issues, projectPath);
// 按风险评分排序
analyzedIssues.sort((a, b) => b.risk_score - a.risk_score);
@ -202,15 +202,15 @@ class AIAnalyzer {
* 批量分析问题
*/
async analyzeBatch(issues, projectPath) {
const prompt = this.buildAnalysisPrompt(issues, projectPath);
const prompt = await this.buildAnalysisPrompt(issues, projectPath);
try {
const requestData = JSON.stringify({
model: this.model,
model: this.openaiModel,
messages: [
{
role: 'system',
content: '你是一个专业的代码质量分析专家,擅长分析Python代码问题,评估风险并提供修改建议。'
content: '你是一个专业的Python代码质量分析专家,擅长分析代码问题,评估风险并提供详细、具体的修改建议。你的建议必须包含问题分析、修复步骤、代码示例和最佳实践。'
},
{
role: 'user',
@ -218,10 +218,10 @@ class AIAnalyzer {
}
],
temperature: 0.3,
max_tokens: 2000
max_tokens: 8000 // 增加token数量以支持一次性处理所有问题和完整代码
});
const url = new URL(`${this.apiBase}/chat/completions`);
const url = new URL(`${this.openaiApiBase}/chat/completions`);
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
@ -229,7 +229,7 @@ class AIAnalyzer {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
'Authorization': `Bearer ${this.openaiApiKey}`,
'Content-Length': Buffer.byteLength(requestData)
}
};
@ -258,17 +258,72 @@ class AIAnalyzer {
req.end();
});
const analysisResult = JSON.parse(data.choices[0].message.content);
// 提取AI返回的内容
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分析-OpenAI] 收到响应,长度: ${aiContent.length} 字符`);
console.log(`[AI分析-OpenAI] 响应内容预览: ${aiContent.substring(0, 300)}`);
// 解析AI响应中的JSON
let analysisResult;
try {
// 尝试直接解析
analysisResult = JSON.parse(aiContent);
} catch (e) {
// 如果不是纯JSON尝试提取JSON部分
console.log('[AI分析-OpenAI] 直接解析失败尝试提取JSON部分');
const jsonMatch = aiContent.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
analysisResult = JSON.parse(jsonMatch[0]);
console.log('[AI分析-OpenAI] 成功提取并解析JSON');
} catch (e2) {
console.error('[AI分析-OpenAI] 提取的JSON解析失败:', e2);
console.error('[AI分析-OpenAI] 响应内容:', aiContent.substring(0, 1000));
throw new Error('无法解析AI响应为JSON: ' + e2.message);
}
} else {
console.error('[AI分析-OpenAI] 响应中未找到JSON格式');
console.error('[AI分析-OpenAI] 响应内容:', aiContent.substring(0, 1000));
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[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建议过短或为空使用规则基础建议`);
console.warn(`[AI分析-OpenAI] 原始建议: ${aiSuggestion}`);
} else {
console.log(`[AI分析-OpenAI] 问题 ${index + 1} 获得AI建议长度: ${aiSuggestion.length} 字符`);
}
return {
...issue,
risk_level: analysis.risk_level || this.assessRisk(issue).level,
risk_score: analysis.risk_score || this.assessRisk(issue).score,
suggestion: analysis.suggestion || this.generateSuggestion(issue),
ai_analyzed: true
risk_score: analysis.risk_score !== undefined ? analysis.risk_score : this.assessRisk(issue).score,
suggestion: (aiSuggestion && aiSuggestion.trim().length >= 20) ? aiSuggestion : this.generateSuggestion(issue),
ai_analyzed: (aiSuggestion && aiSuggestion.trim().length >= 20)
};
});
} catch (error) {
@ -288,11 +343,12 @@ class AIAnalyzer {
}
}
/**
* 使用科大讯飞API批量分析问题HTTP方式
*/
async analyzeBatchXf(issues, projectPath) {
const prompt = this.buildAnalysisPrompt(issues, projectPath);
const prompt = await this.buildAnalysisPrompt(issues, projectPath);
try {
// 构建请求数据 - 使用类似OpenAI的格式
@ -301,32 +357,44 @@ class AIAnalyzer {
messages: [
{
role: 'system',
content: '你是一个专业的代码质量分析专家,擅长分析Python代码问题,评估风险并提供修改建议。'
content: '你是一个专业的Python代码质量分析专家,擅长分析代码问题,评估风险并提供详细、具体的修改建议。你的建议必须包含问题分析、修复步骤、代码示例和最佳实践。'
},
{
role: 'user',
content: `${prompt}\n\n请以JSON格式返回分析结果格式如下\n{\n "issues": [\n {\n "risk_level": "high|medium|low",\n "risk_score": 0-100,\n "suggestion": "具体的修改建议"\n }\n ]\n}`
content: prompt
}
],
temperature: 0.3,
max_tokens: 2000
max_tokens: 8000 // 增加token数量以支持一次性处理所有问题和完整代码
});
// 解析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,
path: url.pathname,
method: 'POST',
headers: {
'Authorization': `Bearer ${this.xfApiPassword}`, // 使用Bearer Token
'Content-Type': 'application/json',
'Authorization': `Basic ${Buffer.from(`${this.xfApiKey}:${this.xfApiSecret}`).toString('base64')}`,
'Content-Length': Buffer.byteLength(requestData)
}
};
console.log(`[AI分析-讯飞] 发送HTTP请求到: ${this.xfApiUrl}`);
console.log(`[AI分析-讯飞] 使用模型: ${this.xfModel}`);
console.log(`[AI分析-讯飞] 请求头部:`);
console.log(` Authorization: Bearer ${this.xfApiPassword.substring(0, 10)}...`);
console.log(` Content-Type: application/json`);
console.log(` Content-Length: ${Buffer.byteLength(requestData)}`);
const data = await new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
@ -369,28 +437,150 @@ 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 {
// 尝试直接解析
analysisResult = JSON.parse(aiContent);
console.log('[AI分析-讯飞] 直接解析JSON成功');
} catch (e) {
// 如果不是纯JSON尝试提取JSON部分
console.log('[AI分析-讯飞] 直接解析失败尝试提取JSON部分');
const jsonMatch = aiContent.match(/\{[\s\S]*\}/);
if (jsonMatch) {
console.log('[AI分析-讯飞] 错误信息:', e.message);
// 方法1: 尝试提取markdown代码块中的JSON
let jsonText = '';
const codeBlockMatch = aiContent.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
if (codeBlockMatch) {
jsonText = codeBlockMatch[1];
console.log('[AI分析-讯飞] 从markdown代码块中提取JSON');
} else {
// 方法2: 尝试提取第一个完整的JSON对象
const jsonMatch = aiContent.match(/\{[\s\S]*\}/);
if (jsonMatch) {
jsonText = jsonMatch[0];
console.log('[AI分析-讯飞] 从响应中提取JSON对象');
}
}
if (jsonText) {
try {
analysisResult = JSON.parse(jsonMatch[0]);
// 尝试清理JSON文本移除可能的控制字符
jsonText = jsonText.replace(/[\x00-\x1F\x7F]/g, '');
analysisResult = JSON.parse(jsonText);
console.log('[AI分析-讯飞] 成功提取并解析JSON');
} catch (e2) {
console.error('[AI分析-讯飞] 提取的JSON解析失败:', e2);
console.error('[AI分析-讯飞] 响应内容:', aiContent.substring(0, 500));
throw new Error('无法解析AI响应为JSON: ' + e2.message);
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) {
// 字符串开始
inString = true;
fixedJson += char;
} else {
// 在字符串中遇到引号
// 检查后面的字符,判断这是字符串结束还是字符串值中的引号
let j = i + 1;
// 跳过空白字符
while (j < jsonText.length && /\s/.test(jsonText[j])) {
j++;
}
if (j >= jsonText.length) {
// 到达末尾,这是字符串结束
inString = false;
fixedJson += char;
} else {
const nextNonSpace = jsonText[j];
// 如果下一个非空白字符是:或,或}或],说明这是字符串结束
if (nextNonSpace === ':' || nextNonSpace === ',' || nextNonSpace === '}' || nextNonSpace === ']') {
inString = false;
fixedJson += char;
} else {
// 这是字符串值中的引号,需要转义
fixedJson += '\\"';
}
}
}
} else {
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) {
const start = Math.max(0, errorPos - 100);
const end = Math.min(jsonText.length, errorPos + 100);
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格式是否正确。`);
}
}
} else {
console.error('[AI分析-讯飞] 响应中未找到JSON格式');
console.error('[AI分析-讯飞] 响应内容:', aiContent.substring(0, 500));
console.error('[AI分析-讯飞] 完整响应内容:', aiContent);
throw new Error('AI响应中未找到有效的JSON格式');
}
}
@ -398,21 +588,67 @@ class AIAnalyzer {
// 验证分析结果格式
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] : {};
return {
...issue,
risk_level: analysis.risk_level || this.assessRisk(issue).level,
risk_score: analysis.risk_score || this.assessRisk(issue).score,
suggestion: analysis.suggestion || this.generateSuggestion(issue),
ai_analyzed: true
};
const aiSuggestion = analysis.suggestion || '';
// 如果AI返回的数组长度不够或者建议为空/过短,使用规则基础建议
if (index >= analysisResult.issues.length) {
console.warn(`[AI分析-讯飞] 问题 ${index + 1} 超出AI返回范围使用规则基础建议`);
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
};
}
// 验证AI返回的建议是否有效
if (!aiSuggestion || aiSuggestion.trim().length < 20) {
console.warn(`[AI分析-讯飞] 问题 ${index + 1} 的AI建议过短或为空使用规则基础建议`);
console.warn(`[AI分析-讯飞] 原始建议: "${aiSuggestion}" (长度: ${aiSuggestion.length})`);
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
};
} 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: aiSuggestion,
ai_analyzed: true
};
}
});
} catch (error) {
console.error('[AI分析-讯飞] 批量分析失败:', error);
@ -434,37 +670,129 @@ class AIAnalyzer {
/**
* 构建AI分析提示词
* 优化一次性处理所有问题按文件分组读取完整文件内容
*/
buildAnalysisPrompt(issues, projectPath) {
const issuesText = issues.map((issue, index) => {
return `${index + 1}. 文件: ${issue.relative_path || issue.file}
行号: ${issue.line}
列号: ${issue.column || 0}
规则: ${issue.rule || '未知'}
消息: ${issue.message || '无'}
类型: ${issue.type || 'unknown'}
严重程度: ${issue.severity || 'medium'}
检测工具: ${(issue.detected_by || []).join(', ')}`;
}).join('\n\n');
return `请分析以下Python代码检查问题对每个问题提供
1. 风险等级high/medium/low
2. 风险评分0-100数值越高风险越大
3. 具体的修改建议
问题列表
async buildAnalysisPrompt(issues, projectPath) {
// 按文件分组问题
const issuesByFile = new Map();
for (const issue of issues) {
const filePath = issue.relative_path || issue.file;
if (!issuesByFile.has(filePath)) {
issuesByFile.set(filePath, []);
}
issuesByFile.get(filePath).push(issue);
}
console.log(`[AI分析] 问题分布在 ${issuesByFile.size} 个文件中`);
// 读取每个文件的完整内容
const filesData = new Map();
for (const [filePath, fileIssues] of issuesByFile.entries()) {
try {
if (filePath && projectPath) {
const fullPath = path.join(projectPath, filePath);
if (fs.existsSync(fullPath)) {
const content = fs.readFileSync(fullPath, 'utf8');
filesData.set(filePath, {
content: content,
issues: fileIssues,
lineCount: content.split('\n').length
});
console.log(`[AI分析] 读取文件: ${filePath} (${content.split('\n').length} 行)`);
}
}
} catch (error) {
console.warn(`[AI分析] 无法读取文件 ${filePath}: ${error.message}`);
}
}
// 构建问题列表(包含完整文件内容)
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`;
issuesText += ` 消息: ${issue.message || '无'}\n`;
issuesText += ` 类型: ${issue.type || 'unknown'}\n`;
issuesText += ` 严重程度: ${issue.severity || 'medium'}\n`;
issuesText += ` 检测工具: ${(issue.detected_by || []).join(', ')}\n\n`;
issueIndex++;
}
}
// 对于没有文件内容的问题,单独列出
for (const issue of issues) {
const filePath = issue.relative_path || issue.file;
if (!filesData.has(filePath)) {
issuesText += `${issueIndex}. 文件: ${filePath}\n`;
issuesText += ` 行号: ${issue.line}\n`;
issuesText += ` 规则: ${issue.rule || '未知'}\n`;
issuesText += ` 消息: ${issue.message || '无'}\n\n`;
issueIndex++;
}
}
return `你是一个专业的Python代码质量分析专家。请分析以下代码检查问题并提供统一的修复建议。
**重要任务**
1. **合并重复问题**首先识别并合并重复或相似的问题不同工具可能检测到相同的问题
2. **统一分析**对所有问题包括合并后的进行统一分析
3. **提供详细建议**为每个问题提供详细的修复建议
**分析要求**
1. **风险等级**high/medium/low根据问题的严重程度和影响范围评估
2. **风险评分**0-100数值越高表示风险越大需要优先处理
3. **修改建议**必须提供详细且具体的修复建议包括
- 问题原因分析
- 具体的修复步骤
- 修复前后的代码示例对比
- 最佳实践建议
- 避免类似问题的建议
**重要要求**
- **必须为所有问题提供建议**返回的issues数组长度必须与原始问题数量完全一致 ${issues.length} 个问题
- **每个问题都必须有suggestion字段**suggestion字段不能为空必须包含至少50字的详细建议
- **按顺序对应**issues数组中的第1个元素对应第1个问题第2个元素对应第2个问题以此类推
- **如果问题已合并**在对应位置返回合并后的分析结果但必须确保数组长度不变
**注意**
- 请先合并重复问题然后对所有问题包括合并后的进行分析
- 根据完整代码上下文给出针对性建议
- 建议要具体可操作避免空泛的描述
- **绝对不能省略任何问题**即使问题相似或重复也必须为每个问题提供独立的分析结果
问题列表包含完整代码
${issuesText}
请以JSON格式返回格式如下
**请严格按照以下JSON格式返回分析结果必须确保**
1. issues数组长度 = ${issues.length}与原始问题数量完全一致
2. 每个issue对象都包含完整的suggestion字段至少50字
3. 所有字段都必须有值不能为空
4. **只返回纯JSON不要包含任何markdown代码块标记不要使用\`\`\`json\`\`\`包裹)**
5. **JSON中的字符串值必须正确转义**换行符使用\\n引号使用\\"反斜杠使用\\\\
**返回格式纯JSON不要任何其他文本**
{
"issues": [
{
"risk_level": "high|medium|low",
"risk_score": 0-100,
"suggestion": "具体的修改建议,包括代码示例"
"suggestion": "详细的问题分析、修复步骤、代码示例和最佳实践建议至少50字不能为空字符串中的特殊字符必须正确转义"
}
]
}`;
}
**重要提示**
- 只返回JSON对象不要包含任何解释文字
- 不要使用markdown代码块包裹JSON
- JSON字符串中的换行符引号等特殊字符必须正确转义
- 必须返回 ${issues.length} 个issues每个都必须有完整的suggestion**`;
}
/**

@ -67,13 +67,18 @@ echo ==========================================
echo.
echo 设置科大讯飞API配置...
set AI_PROVIDER=xf
set XF_APP_ID=96c72c9e
REM HTTP API使用APIpasswordBearer Token在控制台获取https://console.xfyun.cn/services/bmx1
REM 如果使用WebSocket协议则需要XF_API_KEY和XF_API_SECRET
set XF_API_PASSWORD=mfxzmIRlAymtEmsgVojM:ZZIvBMLpPhAHSeATcoDY
REM 兼容旧配置如果XF_API_PASSWORD未设置会尝试使用XF_API_KEY
set XF_API_KEY=mfxzmIRlAymtEmsgVojM
set XF_API_SECRET=ZZIvBMLpPhAHSeATcoDY
set XF_MODEL=x1
set XF_MODEL=spark-x
set XF_API_URL=https://spark-api-open.xf-yun.com/v2/chat/completions
set AI_ANALYZER_ENABLED=true
echo [提示] AI分析服务已配置为使用科大讯飞Spark X1.5 HTTP API
echo [提示] 请确保XF_API_PASSWORD已正确配置HTTP协议的APIpassword
echo [提示] 获取地址https://console.xfyun.cn/services/bmx1
echo.
echo ==========================================

@ -0,0 +1,424 @@
# 配置中心设计文档
## 概述
配置中心用于集中管理系统中的所有配置项支持通过环境变量、配置文件或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` 排除配置文件
---
## 总结
配置中心应包含以上所有配置项,支持:
- ✅ 环境变量配置
- ✅ 配置文件管理
- ✅ 配置验证
- ✅ 配置优先级
- ✅ 配置文档化
- ✅ 敏感信息保护
通过集中管理配置,可以:
- 提高系统的可维护性
- 便于环境切换(开发/测试/生产)
- 增强系统安全性
- 简化部署流程
Loading…
Cancel
Save