Files
code_scan/report/generator.py
Dang Zerong 8594cf4d77 init
2026-03-10 11:18:39 +08:00

240 lines
8.1 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Markdown 报告生成器
生成代码质量扫描报告
"""
import os
import json
import logging
from datetime import datetime
from typing import Dict, Any, Optional
logger = logging.getLogger(__name__)
class ReportGenerator:
"""代码质量扫描报告生成器"""
def __init__(self, config: Dict[str, Any]):
"""
初始化报告生成器
Args:
config: 报告配置
"""
self.config = config
self.output_dir = config.get('output_dir', './reports')
self.keep_files = config.get('keep_files', True)
# 确保输出目录存在
os.makedirs(self.output_dir, exist_ok=True)
def generate(
self,
repo_name: str,
branch: str,
commit_id: str,
commit_message: str,
author: str,
scan_results: Dict[str, Any],
pr_url: str = None,
target_branch: str = None
) -> Dict[str, Any]:
"""
生成扫描报告
Args:
repo_name: 仓库名称
branch: 分支名
commit_id: 提交 ID
commit_message: 提交信息
author: 提交者
scan_results: 扫描结果
pr_url: PR 链接(可选)
target_branch: 目标分支(可选,用于 PR 扫描)
Returns:
报告数据
"""
# 计算总体统计
total_issues = 0
total_errors = 0
total_warnings = 0
for scanner_name, result in scan_results.items():
summary = result.get('summary', {})
total_issues += summary.get('total', 0)
total_errors += summary.get('error', 0) + summary.get('high', 0)
total_warnings += summary.get('warning', 0) + summary.get('medium', 0)
# 确定状态
if total_issues == 0:
status = 'pass'
status_text = '✅ 扫描通过'
elif total_errors > 0:
status = 'fail'
status_text = f'❌ 发现 {total_errors} 个错误'
else:
status = 'warning'
status_text = f'⚠️ 发现 {total_warnings} 个警告'
# 生成报告数据
report = {
'repo_name': repo_name,
'branch': branch,
'commit_id': commit_id,
'commit_message': commit_message,
'author': author,
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'status': status,
'status_text': status_text,
'total_issues': total_issues,
'total_errors': total_errors,
'total_warnings': total_warnings,
'scan_results': scan_results,
'pr_url': pr_url,
'target_branch': target_branch,
'markdown': self._generate_markdown(
repo_name, branch, commit_id, commit_message, author, scan_results, status, status_text, pr_url, target_branch
)
}
# 保存报告文件
if self.keep_files:
self._save_report(report)
return report
def _generate_markdown(
self,
repo_name: str,
branch: str,
commit_id: str,
commit_message: str,
author: str,
scan_results: Dict[str, Any],
status: str,
status_text: str,
pr_url: str = None,
target_branch: str = None
) -> str:
"""生成 Markdown 格式的报告"""
lines = []
# 标题 - 根据是否为 PR 扫描显示不同标题
if pr_url:
lines.append('# 📊 PR 代码质量扫描报告')
else:
lines.append('# 📊 代码质量扫描报告')
lines.append('')
# 基本信息
lines.append('## 📋 基本信息')
lines.append('')
lines.append(f'| 项目 | 内容 |')
lines.append(f'|------|------|')
lines.append(f'| 仓库 | `{repo_name}` |')
# 如果是 PR显示 PR 特有信息
if pr_url and target_branch:
lines.append(f'| 源分支 | `{branch}` |')
lines.append(f'| 目标分支 | `{target_branch}` |')
lines.append(f'| PR 链接 | [查看 PR]({pr_url}) |')
else:
lines.append(f'| 分支 | `{branch}` |')
lines.append(f'| 提交 | `{commit_id}` |')
lines.append(f'| 提交者 | {author} |')
lines.append(f'| 提交信息 | {commit_message[:50]}... |' if len(commit_message) > 50 else f'| 提交信息 | {commit_message} |')
lines.append(f'| 扫描时间 | {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} |')
lines.append('')
# 扫描状态
lines.append('## 📈 扫描状态')
lines.append('')
lines.append(f'**{status_text}**')
lines.append('')
# 各扫描器结果汇总
lines.append('## 🔍 扫描详情')
lines.append('')
for scanner_name, result in scan_results.items():
tool_name = result.get('tool', scanner_name)
summary = result.get('summary', {})
lines.append(f'### {tool_name}')
lines.append('')
lines.append(f'- 扫描文件数: {result.get("files_scanned", 0)}')
lines.append(f'- 总问题数: {summary.get("total", 0)}')
# 根据不同扫描器显示不同的摘要字段
if 'error' in summary:
lines.append(f' - 错误: {summary.get("error", 0)}')
lines.append(f' - 警告: {summary.get("warning", 0)}')
lines.append(f' - 提示: {summary.get("info", 0)}')
elif 'high' in summary:
lines.append(f' - 高危: {summary.get("high", 0)}')
lines.append(f' - 中危: {summary.get("medium", 0)}')
lines.append(f' - 低危: {summary.get("low", 0)}')
issues = result.get('issues', [])
if issues and self.config.get('detailed', True):
lines.append('')
lines.append('**问题列表:**')
lines.append('')
for i, issue in enumerate(issues[:10], 1): # 最多显示10个
severity = issue.get('severity', 'Unknown')
severity_emoji = {
'HIGH': '🔴',
'MEDIUM': '🟡',
'LOW': '🔵',
'ERROR': '🔴',
'WARNING': '🟡',
'INFO': ''
}.get(severity.upper(), '')
file_path = issue.get('file', 'unknown')
line_num = issue.get('line', 0)
message = issue.get('message', 'No message')
lines.append(f'{i}. {severity_emoji} **{severity}** - `{file_path}:{line_num}`')
lines.append(f' - {message}')
lines.append('')
# 添加报告链接或下一步操作
lines.append('---')
lines.append('')
lines.append('*此报告由 AI Code Quality Scanner 自动生成*')
return '\n'.join(lines)
def _save_report(self, report: Dict[str, Any]):
"""保存报告到文件"""
try:
# 生成文件名
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
repo_name = report['repo_name'].replace('/', '_')
filename = f'{repo_name}_{report["commit_id"]}_{timestamp}.md'
filepath = os.path.join(self.output_dir, filename)
# 写入文件
with open(filepath, 'w', encoding='utf-8') as f:
f.write(report['markdown'])
logger.info(f'报告已保存: {filepath}')
# 同时保存 JSON 格式(便于程序解析)
json_filename = filename.replace('.md', '.json')
json_filepath = os.path.join(self.output_dir, json_filename)
with open(json_filepath, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
logger.info(f'JSON 报告已保存: {json_filepath}')
except Exception as e:
logger.error(f'保存报告失败: {str(e)}')