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规范
This commit is contained in:
Your Name
2026-04-02 23:35:53 +08:00
parent ed0961d486
commit 89104bd0db
94 changed files with 24738 additions and 5 deletions

View File

@@ -0,0 +1,312 @@
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
}