新增三库第13号迁移和历史范围回填,保留已删除资源的任务与审计。补充仓储、调用链、身份来源及迁移失败重试回归,同步任务清单、支持矩阵和运维边界。项目成员授权与生产验收仍待完成。
95 lines
2.9 KiB
Go
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
|
|
}
|