Loading...
Loading...
02-reusable-code-java/06-service/AuditLogService.java
/**
* 감사 로그(Audit Log) 서비스
* - 비동기(@Async) 로그 기록으로 메인 트랜잭션 성능 영향 최소화
* - 필터 기반 검색 (entityType, action, 날짜 범위, 키워드)
* - 1년 경과 로그 자동 삭제 (@Scheduled, 매일 새벽 3시)
* - 고유 entityType/action 목록 조회 (관리자 필터 UI용)
*
* @source kcsi-smpa-internal
* @extracted 2026-03-08
* @version 1.0.0
*
* @dependencies spring-boot-starter-data-jpa, lombok
*
* @usage 사용 전 준비사항:
* 1. AuditLog 엔티티 클래스 정의 (domain 패키지)
* - entityType, entityId, action, actorName, actorIp, details 필드
* - @Builder 패턴 적용
* 2. AuditLogRepository 인터페이스 정의 (repository 패키지)
* - search(), findDistinctEntityTypes(), findDistinctActions(), deleteOldLogs() 메서드
* 3. @EnableAsync 설정 추가 (Application 또는 Config 클래스)
*/
package com.example.app.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 감사 로그 서비스
*
* 사용 전 준비사항:
* 1. AuditLog 엔티티 정의
* 2. AuditLogRepository 인터페이스 정의
* 3. 아래 TODO 주석의 import 및 주입 코드 활성화
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AuditLogService {
// TODO: 프로젝트의 AuditLog 엔티티 및 AuditLogRepository로 교체
// private final AuditLogRepository auditLogRepository;
/**
* 감사 로그 기록 (비동기)
* @param entityType 엔티티 유형 (예: "USER", "ORDER", "BOARD")
* @param entityId 엔티티 ID
* @param action 수행 동작 (예: "CREATE", "UPDATE", "DELETE", "LOGIN")
* @param actorName 수행자 이름
* @param actorIp 수행자 IP
* @param details 상세 내용 (선택)
*/
@Async
@Transactional
public void log(String entityType, String entityId, String action,
String actorName, String actorIp, String details) {
// TODO: AuditLog 엔티티 및 Repository 구현 후 아래 코드 활성화
// AuditLog auditLog = AuditLog.builder()
// .entityType(entityType)
// .entityId(entityId)
// .action(action)
// .actorName(actorName)
// .actorIp(actorIp)
// .details(details)
// .build();
//
// auditLogRepository.save(auditLog);
log.info("감사 로그: type={}, id={}, action={}, actor={}, ip={}",
entityType, entityId, action, actorName, actorIp);
}
/**
* 감사 로그 기록 (간편 - 이름/상세 생략)
*/
@Async
@Transactional
public void log(String entityType, String entityId, String action, String actorIp) {
log(entityType, entityId, action, null, actorIp, null);
}
/**
* 필터 기반 검색 (관리자 조회용)
* @return 페이징된 감사 로그 목록
*/
@Transactional(readOnly = true)
public Page<?> search(String entityType, String action,
LocalDate startDate, LocalDate endDate,
String keyword, Pageable pageable) {
LocalDateTime start = startDate != null ? startDate.atStartOfDay() : null;
LocalDateTime end = endDate != null ? endDate.plusDays(1).atStartOfDay() : null;
String kw = (keyword != null && !keyword.trim().isEmpty()) ? keyword.trim() : null;
String et = (entityType != null && !entityType.trim().isEmpty()) ? entityType.trim() : null;
String act = (action != null && !action.trim().isEmpty()) ? action.trim() : null;
// TODO: AuditLogRepository.search() 구현 후 활성화
// return auditLogRepository.search(et, act, start, end, kw, pageable);
return Page.empty(pageable);
}
/**
* 고유 entityType 목록 (필터 UI용)
*/
@Transactional(readOnly = true)
public List<String> getDistinctEntityTypes() {
// TODO: AuditLogRepository.findDistinctEntityTypes() 구현 후 활성화
// return auditLogRepository.findDistinctEntityTypes();
return List.of();
}
/**
* 고유 action 목록 (필터 UI용)
*/
@Transactional(readOnly = true)
public List<String> getDistinctActions() {
// TODO: AuditLogRepository.findDistinctActions() 구현 후 활성화
// return auditLogRepository.findDistinctActions();
return List.of();
}
/**
* 1년 경과 로그 자동 삭제 (매일 새벽 3시 실행)
* - @EnableScheduling 설정 필요 (CachingConfig 참조)
*/
@Scheduled(cron = "0 0 3 * * *")
@Transactional
public void cleanupOldLogs() {
LocalDateTime cutoff = LocalDateTime.now().minusYears(1);
// TODO: AuditLogRepository.deleteOldLogs() 구현 후 활성화
// int deleted = auditLogRepository.deleteOldLogs(cutoff);
// if (deleted > 0) {
// log.info("오래된 감사 로그 삭제: {}건", deleted);
// }
log.debug("감사 로그 정리 실행: cutoff={}", cutoff);
}
}