Files
lijiaoqiao/supply-api/internal/audit/service/metrics_service.go
Your Name 89104bd0db feat(P1/P2): 完成TDD开发及P1/P2设计文档
## 设计文档
- multi_role_permission_design: 多角色权限设计 (CONDITIONAL GO)
- audit_log_enhancement_design: 审计日志增强 (CONDITIONAL GO)
- routing_strategy_template_design: 路由策略模板 (CONDITIONAL GO)
- sso_saml_technical_research: SSO/SAML调研 (CONDITIONAL GO)
- compliance_capability_package_design: 合规能力包设计 (CONDITIONAL GO)

## TDD开发成果
- IAM模块: supply-api/internal/iam/ (111个测试)
- 审计日志模块: supply-api/internal/audit/ (40+测试)
- 路由策略模块: gateway/internal/router/ (33+测试)
- 合规能力包: gateway/internal/compliance/ + scripts/ci/compliance/

## 规范文档
- parallel_agent_output_quality_standards: 并行Agent产出质量规范
- project_experience_summary: 项目经验总结 (v2)
- 2026-04-02-p1-p2-tdd-execution-plan: TDD执行计划

## 评审报告
- 5个CONDITIONAL GO设计文档评审报告
- fix_verification_report: 修复验证报告
- full_verification_report: 全面质量验证报告
- tdd_module_quality_verification: TDD模块质量验证
- tdd_execution_summary: TDD执行总结

依据: Superpowers执行框架 + TDD规范
2026-04-02 23:35:53 +08:00

312 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
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.
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
}