Files
code_scan/report/generator.py

221 lines
7.4 KiB
Python
Raw Normal View History

2026-03-09 09:24:08 +08:00
#!/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]
) -> Dict[str, Any]:
"""
生成扫描报告
Args:
repo_name: 仓库名称
branch: 分支名
commit_id: 提交 ID
commit_message: 提交信息
author: 提交者
scan_results: 扫描结果
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,
'markdown': self._generate_markdown(
repo_name, branch, commit_id, commit_message, author, scan_results, status, status_text
)
}
# 保存报告文件
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
) -> str:
"""生成 Markdown 格式的报告"""
lines = []
# 标题
lines.append('# 📊 代码质量扫描报告')
lines.append('')
# 基本信息
lines.append('## 📋 基本信息')
lines.append('')
lines.append(f'| 项目 | 内容 |')
lines.append(f'|------|------|')
lines.append(f'| 仓库 | `{repo_name}` |')
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)}')