package service import ( "context" "time" "lijiaoqiao/supply-api/internal/audit/model" ) // Metric 指标结构 type Metric struct { MetricID string `json:"metric_id"` MetricName string `json:"metric_name"` Period *MetricPeriod `json:"period"` Value float64 `json:"value"` Unit string `json:"unit"` Status string `json:"status"` // PASS/FAIL Details map[string]interface{} `json:"details"` } // MetricPeriod 指标周期 type MetricPeriod struct { Start time.Time `json:"start"` End time.Time `json:"end"` } // MetricsService 指标服务 type MetricsService struct { auditSvc *AuditService } // NewMetricsService 创建指标服务 func NewMetricsService(auditSvc *AuditService) *MetricsService { return &MetricsService{ auditSvc: auditSvc, } } // CalculateM013 计算M-013指标:凭证泄露事件数 = 0 func (s *MetricsService) CalculateM013(ctx context.Context, start, end time.Time) (*Metric, error) { filter := &EventFilter{ StartTime: start, EndTime: end, Limit: 10000, } events, _, err := s.auditSvc.ListEventsWithFilter(ctx, filter) if err != nil { return nil, err } // 统计CRED-EXPOSE事件数 exposureCount := 0 unresolvedCount := 0 for _, e := range events { if model.IsM013Event(e.EventName) { exposureCount++ // 检查是否已解决(通过扩展字段或标记判断) if s.isEventUnresolved(e) { unresolvedCount++ } } } metric := &Metric{ MetricID: "M-013", MetricName: "supplier_credential_exposure_events", Period: &MetricPeriod{ Start: start, End: end, }, Value: float64(exposureCount), Unit: "count", Status: "PASS", Details: map[string]interface{}{ "total_exposure_events": exposureCount, "unresolved_events": unresolvedCount, }, } // 判断状态:M-013要求暴露事件数为0 if exposureCount > 0 { metric.Status = "FAIL" } return metric, nil } // CalculateM014 计算M-014指标:平台凭证入站覆盖率 = 100% // 分母定义:经平台凭证校验的入站请求(credential_type = 'platform_token'),不含被拒绝的无效请求 func (s *MetricsService) CalculateM014(ctx context.Context, start, end time.Time) (*Metric, error) { filter := &EventFilter{ StartTime: start, EndTime: end, Limit: 10000, } events, _, err := s.auditSvc.ListEventsWithFilter(ctx, filter) if err != nil { return nil, err } // 统计CRED-INGRESS-PLATFORM事件(只有这个才算入M-014) var platformCount, totalIngressCount int for _, e := range events { // M-014只统计CRED-INGRESS-PLATFORM事件 if e.EventName == "CRED-INGRESS-PLATFORM" { totalIngressCount++ // M-014分母:platform_token请求 if e.CredentialType == model.CredentialTypePlatformToken { platformCount++ } } } // 计算覆盖率 var coveragePct float64 if totalIngressCount > 0 { coveragePct = float64(platformCount) / float64(totalIngressCount) * 100 } else { coveragePct = 100.0 // 没有入站请求时,默认为100% } metric := &Metric{ MetricID: "M-014", MetricName: "platform_credential_ingress_coverage_pct", Period: &MetricPeriod{ Start: start, End: end, }, Value: coveragePct, Unit: "percentage", Status: "PASS", Details: map[string]interface{}{ "platform_token_requests": platformCount, "total_requests": totalIngressCount, "non_compliant_requests": totalIngressCount - platformCount, }, } // 判断状态:M-014要求覆盖率为100% if coveragePct < 100.0 { metric.Status = "FAIL" } return metric, nil } // CalculateM015 计算M-015指标:直连绕过事件数 = 0 func (s *MetricsService) CalculateM015(ctx context.Context, start, end time.Time) (*Metric, error) { filter := &EventFilter{ StartTime: start, EndTime: end, Limit: 10000, } events, _, err := s.auditSvc.ListEventsWithFilter(ctx, filter) if err != nil { return nil, err } // 统计CRED-DIRECT事件数 directCallCount := 0 blockedCount := 0 for _, e := range events { if model.IsM015Event(e.EventName) { directCallCount++ // 检查是否被阻断 if s.isEventBlocked(e) { blockedCount++ } } } metric := &Metric{ MetricID: "M-015", MetricName: "direct_supplier_call_by_consumer_events", Period: &MetricPeriod{ Start: start, End: end, }, Value: float64(directCallCount), Unit: "count", Status: "PASS", Details: map[string]interface{}{ "total_direct_call_events": directCallCount, "blocked_events": blockedCount, }, } // 判断状态:M-015要求直连事件数为0 if directCallCount > 0 { metric.Status = "FAIL" } return metric, nil } // CalculateM016 计算M-016指标:query key外部拒绝率 = 100% // 分母定义:检测到的所有query key请求,含被拒绝的请求 func (s *MetricsService) CalculateM016(ctx context.Context, start, end time.Time) (*Metric, error) { filter := &EventFilter{ StartTime: start, EndTime: end, Limit: 10000, } events, _, err := s.auditSvc.ListEventsWithFilter(ctx, filter) if err != nil { return nil, err } // 统计AUTH-QUERY-*事件 var totalQueryKey, rejectedCount int rejectBreakdown := make(map[string]int) for _, e := range events { if model.IsM016Event(e.EventName) { totalQueryKey++ if e.EventName == "AUTH-QUERY-REJECT" { rejectedCount++ rejectBreakdown[e.ResultCode]++ } } } // 计算拒绝率 var rejectRate float64 if totalQueryKey > 0 { rejectRate = float64(rejectedCount) / float64(totalQueryKey) * 100 } else { rejectRate = 100.0 // 没有query key请求时,默认为100% } metric := &Metric{ MetricID: "M-016", MetricName: "query_key_external_reject_rate_pct", Period: &MetricPeriod{ Start: start, End: end, }, Value: rejectRate, Unit: "percentage", Status: "PASS", Details: map[string]interface{}{ "rejected_requests": rejectedCount, "total_external_query_key_requests": totalQueryKey, "reject_breakdown": rejectBreakdown, }, } // 判断状态:M-016要求拒绝率为100%(所有外部query key请求都被拒绝) if rejectRate < 100.0 { metric.Status = "FAIL" } return metric, nil } // isEventUnresolved 检查事件是否未解决 func (s *MetricsService) isEventUnresolved(e *model.AuditEvent) bool { // 如果事件成功,表示已处理/已解决 // 如果事件失败,表示有问题/未解决 return !e.Success } // isEventBlocked 检查直连事件是否被阻断 func (s *MetricsService) isEventBlocked(e *model.AuditEvent) bool { // 通过检查扩展字段或Success标志来判断是否被阻断 if e.Success { return false // 成功表示未被阻断 } // 检查扩展字段中的blocked标记 if e.Extensions != nil { if blocked, ok := e.Extensions["blocked"].(bool); ok { return blocked } } // 通过结果码判断 switch e.ResultCode { case "SEC_DIRECT_BYPASS", "SEC_DIRECT_BYPASS_BLOCKED": return true default: return false } } // GetAllMetrics 获取所有M-013~M-016指标 func (s *MetricsService) GetAllMetrics(ctx context.Context, start, end time.Time) ([]*Metric, error) { m013, err := s.CalculateM013(ctx, start, end) if err != nil { return nil, err } m014, err := s.CalculateM014(ctx, start, end) if err != nil { return nil, err } m015, err := s.CalculateM015(ctx, start, end) if err != nil { return nil, err } m016, err := s.CalculateM016(ctx, start, end) if err != nil { return nil, err } return []*Metric{m013, m014, m015, m016}, nil }