add web
This commit is contained in:
154
app.py
154
app.py
@@ -16,6 +16,7 @@ from scanner.python_scanner import PythonScanner
|
||||
from scanner.js_scanner import JavaScriptScanner
|
||||
from scanner.security_scanner import SecurityScanner
|
||||
from scanner.ai_reviewer import AIReviewer
|
||||
from scanner.diff_parser import merge_issues_with_code
|
||||
from report.generator import ReportGenerator
|
||||
from notify.feishu import FeishuNotifier
|
||||
from gitea_client import GiteaClient
|
||||
@@ -232,6 +233,23 @@ def handle_pull_request(payload: Dict[str, Any]) -> Tuple[Dict, int]:
|
||||
clone_url, source_sha, source_branch
|
||||
)
|
||||
|
||||
# 获取 PR 的代码差异,用于将问题与代码片段关联
|
||||
pr_diff = None
|
||||
if '/' in repo_name:
|
||||
repo_owner, repo_name_only = repo_name.split('/', 1)
|
||||
else:
|
||||
repo_owner = 'Bosch_Demo'
|
||||
repo_name_only = repo_name
|
||||
|
||||
try:
|
||||
pr_diff = gitea_client.get_pull_request_diff(repo_owner, repo_name_only, pr_number)
|
||||
logger.info(f"已获取 PR #{pr_number} 的 diff,长度: {len(pr_diff) if pr_diff else 0}")
|
||||
except Exception as e:
|
||||
logger.warning(f"获取 PR diff 失败: {e}")
|
||||
|
||||
# 将问题与代码片段关联
|
||||
scan_details_with_code = merge_issues_with_code(scan_results, pr_diff or '')
|
||||
|
||||
# 生成报告
|
||||
commit_message = f'PR #{pr_number}: {pr_title}'
|
||||
report = report_generator.generate(
|
||||
@@ -259,7 +277,7 @@ def handle_pull_request(payload: Dict[str, Any]) -> Tuple[Dict, int]:
|
||||
'target_branch': target_branch,
|
||||
'author': author
|
||||
}
|
||||
PRScanDB.save_pr_scan(pr_info_for_db, scan_results, report.get('file_path'))
|
||||
PRScanDB.save_pr_scan(pr_info_for_db, scan_results, report.get('file_path'), scan_details_with_code)
|
||||
|
||||
logger.info(f'PR #{pr_number} 扫描完成')
|
||||
|
||||
@@ -455,12 +473,146 @@ def api_get_pr(pr_id):
|
||||
except:
|
||||
pass
|
||||
|
||||
# 返回带代码片段的扫描详情
|
||||
if pr.get('scan_details_with_code') and isinstance(pr['scan_details_with_code'], str):
|
||||
try:
|
||||
pr['scan_details_with_code'] = json.loads(pr['scan_details_with_code'])
|
||||
except:
|
||||
pass
|
||||
|
||||
return jsonify(pr)
|
||||
except Exception as e:
|
||||
logger.error(f'获取 PR 详情失败: {str(e)}')
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/prs/<int:pr_id>/diff')
|
||||
def api_get_pr_diff(pr_id):
|
||||
"""获取 PR 的代码差异"""
|
||||
try:
|
||||
pr = PRScanDB.get_pr_by_id(pr_id)
|
||||
if not pr:
|
||||
return jsonify({'error': 'PR not found'}), 404
|
||||
|
||||
repo_name = pr.get('repo_name', '')
|
||||
pr_number = pr.get('pr_number', 0)
|
||||
|
||||
if not repo_name or not pr_number:
|
||||
return jsonify({'error': 'PR 信息不完整'}), 400
|
||||
|
||||
# 解析 owner 和 repo
|
||||
if '/' in repo_name:
|
||||
owner, repo = repo_name.split('/', 1)
|
||||
else:
|
||||
owner = 'Bosch_Demo' # 默认
|
||||
repo = repo_name
|
||||
|
||||
logger.info(f"获取 PR #{pr_number} ({owner}/{repo}) 的 diff")
|
||||
|
||||
# 获取 diff
|
||||
diff = gitea_client.get_pull_request_diff(owner, repo, pr_number)
|
||||
if diff is None:
|
||||
return jsonify({'error': '获取 diff 失败'}), 500
|
||||
|
||||
return jsonify({
|
||||
'diff': diff,
|
||||
'pr_number': pr_number,
|
||||
'repo_name': repo_name
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'获取 PR diff 失败: {str(e)}')
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/prs/<int:pr_id>/files')
|
||||
def api_get_pr_files(pr_id):
|
||||
"""获取 PR 变更文件列表(用于左侧树状展示)"""
|
||||
try:
|
||||
pr = PRScanDB.get_pr_by_id(pr_id)
|
||||
if not pr:
|
||||
return jsonify({'error': 'PR not found'}), 404
|
||||
repo_name = pr.get('repo_name', '')
|
||||
pr_number = pr.get('pr_number', 0)
|
||||
if not repo_name or not pr_number:
|
||||
return jsonify({'error': 'PR 信息不完整'}), 400
|
||||
if '/' in repo_name:
|
||||
owner, repo = repo_name.split('/', 1)
|
||||
else:
|
||||
owner, repo = 'Bosch_Demo', repo_name
|
||||
files = gitea_client.get_pull_request_files(owner, repo, pr_number)
|
||||
if files is None:
|
||||
return jsonify({'error': '获取文件列表失败'}), 500
|
||||
return jsonify({'files': files, 'repo_name': repo_name})
|
||||
except Exception as e:
|
||||
logger.error(f'获取 PR 文件列表失败: {str(e)}')
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/prs/<int:pr_id>/file')
|
||||
def api_get_pr_file_content(pr_id):
|
||||
"""获取 PR 中某文件在源分支上的完整内容"""
|
||||
try:
|
||||
path = request.args.get('path')
|
||||
if not path:
|
||||
return jsonify({'error': '缺少 path 参数'}), 400
|
||||
pr = PRScanDB.get_pr_by_id(pr_id)
|
||||
if not pr:
|
||||
return jsonify({'error': 'PR not found'}), 404
|
||||
repo_name = pr.get('repo_name', '')
|
||||
pr_number = pr.get('pr_number', 0)
|
||||
if not repo_name or not pr_number:
|
||||
return jsonify({'error': 'PR 信息不完整'}), 400
|
||||
if '/' in repo_name:
|
||||
owner, repo = repo_name.split('/', 1)
|
||||
else:
|
||||
owner, repo = 'Bosch_Demo', repo_name
|
||||
pr_info = gitea_client.get_pull_request(owner, repo, pr_number)
|
||||
if not pr_info:
|
||||
return jsonify({'error': '获取 PR 信息失败'}), 500
|
||||
head_ref = pr_info.get('head', {}).get('ref') or pr_info.get('head_branch') or pr.get('source_branch')
|
||||
if not head_ref:
|
||||
return jsonify({'error': '无法确定源分支'}), 400
|
||||
content = gitea_client.get_file_contents(owner, repo, path, head_ref)
|
||||
if content is None:
|
||||
return jsonify({'error': '文件不存在或无法读取'}), 404
|
||||
|
||||
# 获取该文件的扫描问题(PR 创建时已扫描并存入 scan_details_with_code)
|
||||
scan_issues = []
|
||||
path_norm = path.replace('\\', '/').strip()
|
||||
scan_details = pr.get('scan_details_with_code')
|
||||
if isinstance(scan_details, str):
|
||||
try:
|
||||
scan_details = json.loads(scan_details)
|
||||
except Exception:
|
||||
scan_details = None
|
||||
if scan_details and scan_details.get('scanners'):
|
||||
for scanner in scan_details['scanners']:
|
||||
for issue in scanner.get('issues', []):
|
||||
issue_file = (issue.get('file') or '').replace('\\', '/').strip()
|
||||
if not issue_file:
|
||||
continue
|
||||
# 匹配:精确相等或一端包含另一端(兼容 basename 或完整路径)
|
||||
if path_norm == issue_file or path_norm.endswith(issue_file) or issue_file.endswith(path_norm):
|
||||
sev = (issue.get('severity') or 'info')
|
||||
if isinstance(sev, str):
|
||||
sev = sev.lower()
|
||||
scanner_name = scanner.get('name', '')
|
||||
scanner_display = {'python': 'Python', 'javascript': 'JavaScript', 'security': 'Security'}.get(scanner_name, scanner_name)
|
||||
scan_issues.append({
|
||||
'scanner': scanner_display,
|
||||
'severity': sev,
|
||||
'line': int(issue.get('line') or 0),
|
||||
'message': (issue.get('message') or issue.get('description') or '').strip(),
|
||||
'code_context': issue.get('code_context')
|
||||
})
|
||||
|
||||
return jsonify({'path': path, 'content': content, 'scan_issues': scan_issues})
|
||||
except Exception as e:
|
||||
logger.error(f'获取文件内容失败: {str(e)}')
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/prs/<int:pr_id>/merge', methods=['POST'])
|
||||
def api_merge_pr(pr_id):
|
||||
"""合并 PR"""
|
||||
|
||||
Reference in New Issue
Block a user