feat: 트리거 감사로그 operation 단위 일괄 롤백 기능

- operation 상세 페이지 및 일괄 롤백 실행 기능 추가
- TriggerAuditLog에 scopeForOperation 스코프 추가
- 트리거 INSERT/UPDATE/DELETE에 operation_id 컬럼 포함
- 감사로그 목록에 작업 단위 링크 컬럼 추가
- 라우트: operation/{id}, batch-rollback 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 11:17:45 +09:00
parent 981c3c68d4
commit 9eeb62f819
6 changed files with 282 additions and 9 deletions

View File

@@ -233,6 +233,86 @@ private function buildReinsertSQL(TriggerAuditLog $log, \PDO $pdo): string
return "INSERT INTO `{$log->table_name}` ({$cols}) VALUES ({$vals})";
}
// ── 4.3 일괄 롤백 (Operation 단위) ──────────────────
/**
* Operation 단위 상세 (일괄 롤백 미리보기)
*/
public function operation(string $operationId): View
{
$logs = TriggerAuditLog::forOperation($operationId)
->orderBy('created_at')
->orderBy('id')
->get();
if ($logs->isEmpty()) {
abort(404, '해당 작업 ID의 로그가 없습니다.');
}
// 각 로그별 롤백 SQL 생성
$rollbackItems = $logs->reverse()->map(function (TriggerAuditLog $log) {
return [
'log' => $log,
'sql' => $this->generateRollbackSQL($log),
'diff' => $this->calculateDiff($log),
];
});
// 요약 정보
$summary = [
'operation_id' => $operationId,
'total_count' => $logs->count(),
'tables' => $logs->groupBy('table_name')->map->count(),
'dml_types' => $logs->groupBy('dml_type')->map->count(),
'actor_id' => $logs->first()->actor_id,
'session_info' => $logs->first()->session_info,
'started_at' => $logs->first()->created_at,
'ended_at' => $logs->last()->created_at,
];
return view('trigger-audit.operation', compact('rollbackItems', 'summary'));
}
/**
* Operation 단위 일괄 롤백 실행
*/
public function batchRollbackExecute(Request $request, string $operationId)
{
$request->validate(['confirm' => 'required|accepted']);
$logs = TriggerAuditLog::forOperation($operationId)
->orderBy('created_at')
->orderBy('id')
->get();
if ($logs->isEmpty()) {
abort(404, '해당 작업 ID의 로그가 없습니다.');
}
// 역순으로 롤백 SQL 생성
$sqls = $logs->reverse()->map(fn (TriggerAuditLog $log) => $this->generateRollbackSQL($log));
DB::statement('SET @disable_audit_trigger = 1');
try {
DB::transaction(function () use ($sqls) {
foreach ($sqls as $sql) {
if (! str_starts_with($sql, '--')) {
DB::statement($sql);
}
}
});
return redirect()->route('trigger-audit.operation', $operationId)
->with('success', "일괄 롤백이 성공적으로 실행되었습니다. ({$logs->count()}건)");
} catch (\Throwable $e) {
return redirect()->route('trigger-audit.operation', $operationId)
->with('error', '일괄 롤백 실패: '.$e->getMessage());
} finally {
DB::statement('SET @disable_audit_trigger = NULL');
}
}
// ── 4.4 트리거 관리 ──────────────────────────────
/**