438 lines
13 KiB
Rust
438 lines
13 KiB
Rust
//! 合规报告生成器模块
|
||
//!
|
||
//! 实现报告生成、存储、查询和导出
|
||
|
||
use crate::compliance_layer::*;
|
||
use crate::error::*;
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
use std::path::{Path, PathBuf};
|
||
|
||
/// 报告生成器
|
||
pub struct ReportGenerator {
|
||
/// 报告存储路径
|
||
storage_path: PathBuf,
|
||
/// 报告缓存
|
||
cache: HashMap<String, ComplianceReport>,
|
||
}
|
||
|
||
impl ReportGenerator {
|
||
/// 创建新的报告生成器
|
||
pub fn new() -> Self {
|
||
Self {
|
||
storage_path: std::env::temp_dir().join("nac_compliance_reports"),
|
||
cache: HashMap::new(),
|
||
}
|
||
}
|
||
|
||
/// 设置存储路径
|
||
pub fn with_storage_path(mut self, path: PathBuf) -> Self {
|
||
self.storage_path = path;
|
||
self
|
||
}
|
||
|
||
/// 生成报告
|
||
pub fn generate(&self, results: &[ComplianceResult]) -> Result<ComplianceReport> {
|
||
let report_id = self.generate_report_id();
|
||
|
||
// 计算总体状态
|
||
let overall_status = self.calculate_overall_status(results);
|
||
|
||
// 计算总体风险等级
|
||
let overall_risk = self.calculate_overall_risk(results);
|
||
|
||
// 计算平均置信度
|
||
let avg_confidence = if results.is_empty() {
|
||
0.0
|
||
} else {
|
||
results.iter().map(|r| r.confidence).sum::<f64>() / results.len() as f64
|
||
};
|
||
|
||
// 收集所有问题
|
||
let all_issues: Vec<ComplianceIssue> = results
|
||
.iter()
|
||
.flat_map(|r| r.issues.clone())
|
||
.collect();
|
||
|
||
// 收集所有建议
|
||
let all_recommendations: Vec<String> = results
|
||
.iter()
|
||
.flat_map(|r| r.recommendations.clone())
|
||
.collect();
|
||
|
||
// 生成摘要
|
||
let summary = self.generate_summary(results);
|
||
|
||
Ok(ComplianceReport {
|
||
id: report_id,
|
||
results: results.to_vec(),
|
||
overall_status,
|
||
overall_risk,
|
||
average_confidence: avg_confidence,
|
||
total_issues: all_issues.len(),
|
||
total_recommendations: all_recommendations.len(),
|
||
summary,
|
||
generated_at: chrono::Utc::now(),
|
||
})
|
||
}
|
||
|
||
/// 保存报告
|
||
pub fn save(&mut self, report: &ComplianceReport) -> Result<()> {
|
||
// 创建存储目录
|
||
std::fs::create_dir_all(&self.storage_path)?;
|
||
|
||
// 序列化报告
|
||
let json = serde_json::to_string_pretty(report)?;
|
||
|
||
// 写入文件
|
||
let file_path = self.storage_path.join(format!("{}.json", report.id));
|
||
std::fs::write(&file_path, json)?;
|
||
|
||
// 添加到缓存
|
||
self.cache.insert(report.id.clone(), report.clone());
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 加载报告
|
||
pub fn load(&mut self, report_id: &str) -> Result<ComplianceReport> {
|
||
// 先检查缓存
|
||
if let Some(report) = self.cache.get(report_id) {
|
||
return Ok(report.clone());
|
||
}
|
||
|
||
// 从文件加载
|
||
let file_path = self.storage_path.join(format!("{}.json", report_id));
|
||
let json = std::fs::read_to_string(&file_path)?;
|
||
let report: ComplianceReport = serde_json::from_str(&json)?;
|
||
|
||
// 添加到缓存
|
||
self.cache.insert(report_id.to_string(), report.clone());
|
||
|
||
Ok(report)
|
||
}
|
||
|
||
/// 查询报告
|
||
pub fn query(&self, filter: ReportFilter) -> Result<Vec<ComplianceReport>> {
|
||
let mut reports = Vec::new();
|
||
|
||
// 遍历存储目录
|
||
if !self.storage_path.exists() {
|
||
return Ok(reports);
|
||
}
|
||
|
||
for entry in std::fs::read_dir(&self.storage_path)? {
|
||
let entry = entry?;
|
||
let path = entry.path();
|
||
|
||
if path.extension().and_then(|s| s.to_str()) != Some("json") {
|
||
continue;
|
||
}
|
||
|
||
// 读取报告
|
||
let json = std::fs::read_to_string(&path)?;
|
||
let report: ComplianceReport = serde_json::from_str(&json)?;
|
||
|
||
// 应用过滤器
|
||
if filter.matches(&report) {
|
||
reports.push(report);
|
||
}
|
||
}
|
||
|
||
Ok(reports)
|
||
}
|
||
|
||
/// 导出报告
|
||
pub fn export(&self, report: &ComplianceReport, format: ExportFormat) -> Result<Vec<u8>> {
|
||
match format {
|
||
ExportFormat::Json => {
|
||
let json = serde_json::to_string_pretty(report)?;
|
||
Ok(json.into_bytes())
|
||
}
|
||
ExportFormat::Csv => {
|
||
self.export_csv(report)
|
||
}
|
||
ExportFormat::Pdf => {
|
||
// 简化实现:返回JSON
|
||
// 实际实现需要使用PDF生成库
|
||
let json = serde_json::to_string_pretty(report)?;
|
||
Ok(json.into_bytes())
|
||
}
|
||
ExportFormat::Html => {
|
||
self.export_html(report)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 导出为CSV
|
||
fn export_csv(&self, report: &ComplianceReport) -> Result<Vec<u8>> {
|
||
let mut csv = String::new();
|
||
|
||
// 表头
|
||
csv.push_str("Layer,Status,Confidence,Risk Level,Issues,Recommendations\n");
|
||
|
||
// 数据行
|
||
for result in &report.results {
|
||
csv.push_str(&format!(
|
||
"{},{:?},{:.2},{:?},{},{}\n",
|
||
result.layer.name(),
|
||
result.status,
|
||
result.confidence,
|
||
result.risk_level,
|
||
result.issues.len(),
|
||
result.recommendations.len()
|
||
));
|
||
}
|
||
|
||
Ok(csv.into_bytes())
|
||
}
|
||
|
||
/// 导出为HTML
|
||
fn export_html(&self, report: &ComplianceReport) -> Result<Vec<u8>> {
|
||
let mut html = String::new();
|
||
|
||
html.push_str("<!DOCTYPE html>\n");
|
||
html.push_str("<html>\n<head>\n");
|
||
html.push_str("<meta charset=\"UTF-8\">\n");
|
||
html.push_str("<title>合规报告</title>\n");
|
||
html.push_str("<style>\n");
|
||
html.push_str("body { font-family: Arial, sans-serif; margin: 20px; }\n");
|
||
html.push_str("table { border-collapse: collapse; width: 100%; }\n");
|
||
html.push_str("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n");
|
||
html.push_str("th { background-color: #4CAF50; color: white; }\n");
|
||
html.push_str("</style>\n");
|
||
html.push_str("</head>\n<body>\n");
|
||
|
||
html.push_str(&format!("<h1>合规报告 #{}</h1>\n", report.id));
|
||
html.push_str(&format!("<p>生成时间: {}</p>\n", report.generated_at));
|
||
html.push_str(&format!("<p>总体状态: {:?}</p>\n", report.overall_status));
|
||
html.push_str(&format!("<p>总体风险: {:?}</p>\n", report.overall_risk));
|
||
html.push_str(&format!("<p>平均置信度: {:.2}</p>\n", report.average_confidence));
|
||
|
||
html.push_str("<h2>验证结果</h2>\n");
|
||
html.push_str("<table>\n");
|
||
html.push_str("<tr><th>层级</th><th>状态</th><th>置信度</th><th>风险等级</th><th>问题数</th><th>建议数</th></tr>\n");
|
||
|
||
for result in &report.results {
|
||
html.push_str(&format!(
|
||
"<tr><td>{}</td><td>{:?}</td><td>{:.2}</td><td>{:?}</td><td>{}</td><td>{}</td></tr>\n",
|
||
result.layer.name(),
|
||
result.status,
|
||
result.confidence,
|
||
result.risk_level,
|
||
result.issues.len(),
|
||
result.recommendations.len()
|
||
));
|
||
}
|
||
|
||
html.push_str("</table>\n");
|
||
html.push_str("</body>\n</html>");
|
||
|
||
Ok(html.into_bytes())
|
||
}
|
||
|
||
/// 生成报告ID
|
||
fn generate_report_id(&self) -> String {
|
||
use std::time::{SystemTime, UNIX_EPOCH};
|
||
let timestamp = SystemTime::now()
|
||
.duration_since(UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_millis();
|
||
format!("RPT{}", timestamp)
|
||
}
|
||
|
||
/// 计算总体状态
|
||
fn calculate_overall_status(&self, results: &[ComplianceResult]) -> ComplianceStatus {
|
||
if results.iter().any(|r| r.status == ComplianceStatus::Failed) {
|
||
ComplianceStatus::Failed
|
||
} else if results.iter().any(|r| r.status == ComplianceStatus::ManualReview) {
|
||
ComplianceStatus::ManualReview
|
||
} else if results.iter().any(|r| r.status == ComplianceStatus::ConditionalPass) {
|
||
ComplianceStatus::ConditionalPass
|
||
} else if results.iter().all(|r| r.status == ComplianceStatus::Passed) {
|
||
ComplianceStatus::Passed
|
||
} else {
|
||
ComplianceStatus::Pending
|
||
}
|
||
}
|
||
|
||
/// 计算总体风险
|
||
fn calculate_overall_risk(&self, results: &[ComplianceResult]) -> RiskLevel {
|
||
results
|
||
.iter()
|
||
.map(|r| r.risk_level)
|
||
.max()
|
||
.unwrap_or(RiskLevel::Low)
|
||
}
|
||
|
||
/// 生成摘要
|
||
fn generate_summary(&self, results: &[ComplianceResult]) -> String {
|
||
let passed = results.iter().filter(|r| r.status == ComplianceStatus::Passed).count();
|
||
let failed = results.iter().filter(|r| r.status == ComplianceStatus::Failed).count();
|
||
let manual = results.iter().filter(|r| r.status == ComplianceStatus::ManualReview).count();
|
||
|
||
format!(
|
||
"共验证{}个层级,通过{}个,失败{}个,需人工审核{}个",
|
||
results.len(),
|
||
passed,
|
||
failed,
|
||
manual
|
||
)
|
||
}
|
||
}
|
||
|
||
impl Default for ReportGenerator {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
/// 合规报告
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ComplianceReport {
|
||
/// 报告ID
|
||
pub id: String,
|
||
/// 验证结果列表
|
||
pub results: Vec<ComplianceResult>,
|
||
/// 总体状态
|
||
pub overall_status: ComplianceStatus,
|
||
/// 总体风险等级
|
||
pub overall_risk: RiskLevel,
|
||
/// 平均置信度
|
||
pub average_confidence: f64,
|
||
/// 总问题数
|
||
pub total_issues: usize,
|
||
/// 总建议数
|
||
pub total_recommendations: usize,
|
||
/// 摘要
|
||
pub summary: String,
|
||
/// 生成时间
|
||
pub generated_at: chrono::DateTime<chrono::Utc>,
|
||
}
|
||
|
||
/// 报告过滤器
|
||
#[derive(Debug, Clone, Default)]
|
||
pub struct ReportFilter {
|
||
/// 状态过滤
|
||
pub status: Option<ComplianceStatus>,
|
||
/// 风险等级过滤
|
||
pub risk_level: Option<RiskLevel>,
|
||
/// 开始时间
|
||
pub start_time: Option<chrono::DateTime<chrono::Utc>>,
|
||
/// 结束时间
|
||
pub end_time: Option<chrono::DateTime<chrono::Utc>>,
|
||
}
|
||
|
||
impl ReportFilter {
|
||
/// 检查报告是否匹配过滤器
|
||
pub fn matches(&self, report: &ComplianceReport) -> bool {
|
||
if let Some(status) = self.status {
|
||
if report.overall_status != status {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if let Some(risk_level) = self.risk_level {
|
||
if report.overall_risk != risk_level {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if let Some(start_time) = self.start_time {
|
||
if report.generated_at < start_time {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if let Some(end_time) = self.end_time {
|
||
if report.generated_at > end_time {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
true
|
||
}
|
||
}
|
||
|
||
/// 导出格式
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub enum ExportFormat {
|
||
/// JSON格式
|
||
Json,
|
||
/// CSV格式
|
||
Csv,
|
||
/// PDF格式
|
||
Pdf,
|
||
/// HTML格式
|
||
Html,
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_report_generation() {
|
||
let generator = ReportGenerator::new();
|
||
let results = vec![
|
||
ComplianceResult {
|
||
layer: ComplianceLayer::IdentityVerification,
|
||
status: ComplianceStatus::Passed,
|
||
confidence: 0.9,
|
||
risk_level: RiskLevel::Low,
|
||
details: "Test".to_string(),
|
||
issues: vec![],
|
||
recommendations: vec![],
|
||
timestamp: chrono::Utc::now(),
|
||
}
|
||
];
|
||
|
||
let report = generator.generate(&results).unwrap();
|
||
assert_eq!(report.results.len(), 1);
|
||
assert_eq!(report.overall_status, ComplianceStatus::Passed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_report_export_json() {
|
||
let generator = ReportGenerator::new();
|
||
let results = vec![
|
||
ComplianceResult {
|
||
layer: ComplianceLayer::IdentityVerification,
|
||
status: ComplianceStatus::Passed,
|
||
confidence: 0.9,
|
||
risk_level: RiskLevel::Low,
|
||
details: "Test".to_string(),
|
||
issues: vec![],
|
||
recommendations: vec![],
|
||
timestamp: chrono::Utc::now(),
|
||
}
|
||
];
|
||
|
||
let report = generator.generate(&results).unwrap();
|
||
let exported = generator.export(&report, ExportFormat::Json).unwrap();
|
||
assert!(!exported.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn test_report_filter() {
|
||
let filter = ReportFilter {
|
||
status: Some(ComplianceStatus::Passed),
|
||
..Default::default()
|
||
};
|
||
|
||
let report = ComplianceReport {
|
||
id: "test".to_string(),
|
||
results: vec![],
|
||
overall_status: ComplianceStatus::Passed,
|
||
overall_risk: RiskLevel::Low,
|
||
average_confidence: 0.9,
|
||
total_issues: 0,
|
||
total_recommendations: 0,
|
||
summary: "Test".to_string(),
|
||
generated_at: chrono::Utc::now(),
|
||
};
|
||
|
||
assert!(filter.matches(&report));
|
||
}
|
||
}
|