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:
114
gateway/internal/middleware/audit.go
Normal file
114
gateway/internal/middleware/audit.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
// DatabaseAuditEmitter 实现 AuditEmitter 接口,将审计事件存入数据库
|
||||
type DatabaseAuditEmitter struct {
|
||||
db *sql.DB
|
||||
mu sync.RWMutex
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewDatabaseAuditEmitter 创建数据库审计发射器
|
||||
func NewDatabaseAuditEmitter(dsn string, now func() time.Time) (*DatabaseAuditEmitter, error) {
|
||||
if now == nil {
|
||||
now = time.Now
|
||||
}
|
||||
|
||||
db, err := sql.Open("pgx", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
if err := db.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
emitter := &DatabaseAuditEmitter{
|
||||
db: db,
|
||||
now: now,
|
||||
}
|
||||
|
||||
// 初始化表
|
||||
if err := emitter.initSchema(); err != nil {
|
||||
return nil, fmt.Errorf("failed to init schema: %w", err)
|
||||
}
|
||||
|
||||
return emitter, nil
|
||||
}
|
||||
|
||||
// initSchema 创建审计表
|
||||
func (e *DatabaseAuditEmitter) initSchema() error {
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS token_audit_events (
|
||||
event_id VARCHAR(64) PRIMARY KEY,
|
||||
event_name VARCHAR(128) NOT NULL,
|
||||
request_id VARCHAR(128) NOT NULL,
|
||||
token_id VARCHAR(128),
|
||||
subject_id VARCHAR(128),
|
||||
route VARCHAR(256) NOT NULL,
|
||||
result_code VARCHAR(64) NOT NULL,
|
||||
client_ip VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_token_audit_request_id ON token_audit_events(request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_token_audit_token_id ON token_audit_events(token_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_token_audit_subject_id ON token_audit_events(subject_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_token_audit_created_at ON token_audit_events(created_at);
|
||||
`
|
||||
_, err := e.db.Exec(schema)
|
||||
return err
|
||||
}
|
||||
|
||||
// Emit 实现 AuditEmitter 接口
|
||||
func (e *DatabaseAuditEmitter) Emit(_ context.Context, event AuditEvent) error {
|
||||
if event.EventID == "" {
|
||||
event.EventID = fmt.Sprintf("evt-%d", e.now().UnixNano())
|
||||
}
|
||||
if event.CreatedAt.IsZero() {
|
||||
event.CreatedAt = e.now()
|
||||
}
|
||||
|
||||
query := `
|
||||
INSERT INTO token_audit_events (event_id, event_name, request_id, token_id, subject_id, route, result_code, client_ip, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
`
|
||||
_, err := e.db.Exec(query,
|
||||
event.EventID,
|
||||
event.EventName,
|
||||
event.RequestID,
|
||||
nullString(event.TokenID),
|
||||
nullString(event.SubjectID),
|
||||
event.Route,
|
||||
event.ResultCode,
|
||||
nullString(event.ClientIP),
|
||||
event.CreatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Close 关闭数据库连接
|
||||
func (e *DatabaseAuditEmitter) Close() error {
|
||||
if e.db != nil {
|
||||
return e.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nullString 安全处理空字符串
|
||||
func nullString(s string) sql.NullString {
|
||||
if s == "" {
|
||||
return sql.NullString{}
|
||||
}
|
||||
return sql.NullString{String: s, Valid: true}
|
||||
}
|
||||
Reference in New Issue
Block a user