Scert/backend/internal/repository/audit_repository.go
xuans 0ccdb14b26 完善任务审计范围快照与身份关联并同步整改进度
新增三库第13号迁移和历史范围回填,保留已删除资源的任务与审计。补充仓储、调用链、身份来源及迁移失败重试回归,同步任务清单、支持矩阵和运维边界。项目成员授权与生产验收仍待完成。
2026-09-28 11:48:27 +08:00

95 lines
2.9 KiB
Go

package repository
import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"time"
)
type AuditLog struct {
HistoryScope
TaskID string `json:"task_id"`
OperatorID string `json:"operator_id"`
ID int64 `json:"id"`
Action string `json:"action"`
ResourceType string `json:"resource_type"`
ResourceID string `json:"resource_id"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"created_at"`
}
type AuditRepository interface {
CreateAuditLog(ctx context.Context, item AuditLog) error
}
type SQLAuditRepository struct {
db *sql.DB
driver string
}
func NewSQLAuditRepository(db *sql.DB, driver ...string) *SQLAuditRepository {
return &SQLAuditRepository{db: db, driver: repositoryDriver(driver)}
}
func (r *SQLAuditRepository) query(value string) string {
return Rebind(r.driver, value)
}
func (r *SQLAuditRepository) CreateAuditLog(ctx context.Context, item AuditLog) error {
if r == nil || r.db == nil {
return errors.New("数据库连接不能为空")
}
item.Action = strings.TrimSpace(item.Action)
item.ResourceType = strings.TrimSpace(item.ResourceType)
item.ResourceID = strings.TrimSpace(item.ResourceID)
item.Status = strings.TrimSpace(item.Status)
item.TaskID = strings.TrimSpace(item.TaskID)
item.OperatorID = strings.TrimSpace(item.OperatorID)
if item.Action == "" || item.ResourceType == "" || item.Status == "" {
return errors.New("审计日志必要字段不能为空")
}
if item.CreatedAt.IsZero() {
item.CreatedAt = time.Now().UTC()
}
if item.ResourceType == "task" && item.TaskID == "" {
item.TaskID = item.ResourceID
}
scope, err := r.resolveScope(ctx, item)
if err != nil {
return fmt.Errorf("解析审计范围失败:%w", err)
}
if !item.HistoryScope.matches(scope) {
return errors.New("审计项目与资源范围不匹配")
}
_, err = r.db.ExecContext(ctx, r.query(`INSERT INTO audit_logs
(action, resource_type, resource_id, status, message, created_at, scope_type, project_id, task_id, operator_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), item.Action, item.ResourceType, item.ResourceID, item.Status, item.Message,
item.CreatedAt.UTC(), scope.ScopeType, scope.ProjectID, item.TaskID, item.OperatorID)
if err != nil {
return fmt.Errorf("保存审计日志失败:%w", err)
}
return nil
}
func (r *SQLAuditRepository) PruneExpiredAuditLogs(ctx context.Context, before time.Time) (int64, error) {
if r == nil || r.db == nil {
return 0, errors.New("数据库连接不能为空")
}
if before.IsZero() {
return 0, errors.New("审计日志清理时间不能为空")
}
result, err := r.db.ExecContext(ctx, r.query(`DELETE FROM audit_logs WHERE created_at < ?`), before.UTC())
if err != nil {
return 0, fmt.Errorf("清理过期审计日志失败:%w", err)
}
count, err := result.RowsAffected()
if err != nil {
return 0, fmt.Errorf("读取审计日志清理数量失败:%w", err)
}
return count, nil
}