// audit.go:合规审计表(denied/redact 记录,design-arch §5.4 / §7.2)。 package store import ( "fmt" "time" "onesvm.com/onesvm/browser-server/internal/config" ) // AuditRow 审计记录。 type AuditRow struct { ID int64 ConsumerID int64 // 0 = 匿名/未认证 URL string RuleID string TS time.Time } // AuditAppend 写一条审计(能停能报最低限度)。 func (d *DB) AuditAppend(consumerID int64, url, ruleID string) error { _, err := d.raw.Exec( `INSERT INTO audit(consumer_id, url, rule_id, ts) VALUES(?, ?, ?, ?)`, consumerID, url, ruleID, config.Now().Format(time.RFC3339)) if err != nil { return fmt.Errorf("store: AuditAppend: %w", err) } return nil } // AuditRecent 按时间倒序取最近 n 条(运维排查用,禁 SELECT *)。 func (d *DB) AuditRecent(n int) ([]AuditRow, error) { rows, err := d.raw.Query( `SELECT id, consumer_id, url, rule_id, ts FROM audit ORDER BY ts DESC, id DESC LIMIT ?`, n) if err != nil { return nil, fmt.Errorf("store: AuditRecent: %w", err) } defer rows.Close() var out []AuditRow for rows.Next() { var r AuditRow var ts string if err := rows.Scan(&r.ID, &r.ConsumerID, &r.URL, &r.RuleID, &ts); err != nil { return nil, fmt.Errorf("store: AuditRecent scan: %w", err) } t, err := time.Parse(time.RFC3339, ts) if err != nil { return nil, fmt.Errorf("store: AuditRecent ts 解析: %w", err) } r.TS = t out = append(out, r) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("store: AuditRecent rows: %w", err) } return out, nil } // AuditCountByRule 统计某规则命中次数(/metrics denied_total{rule_id})。 func (d *DB) AuditCountByRule(ruleID string) (int, error) { var n int if err := d.raw.QueryRow( `SELECT COUNT(id) FROM audit WHERE rule_id = ?`, ruleID).Scan(&n); err != nil { return 0, fmt.Errorf("store: AuditCountByRule: %w", err) } return n, nil }