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:
@@ -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 트리거 관리 ──────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user