다운로드 호출 로그관리 작업
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
package com.kamco.cd.kamcoback.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
|
||||||
|
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
|
||||||
|
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||||
|
import com.kamco.cd.kamcoback.menu.service.MenuService;
|
||||||
|
import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity;
|
||||||
|
import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class FileDownloadInteceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private final AuditLogRepository auditLogRepository;
|
||||||
|
private final MenuService menuService;
|
||||||
|
|
||||||
|
@Autowired private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public FileDownloadInteceptor(AuditLogRepository auditLogRepository, MenuService menuService) {
|
||||||
|
this.auditLogRepository = auditLogRepository;
|
||||||
|
this.menuService = menuService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||||
|
|
||||||
|
// 파일 다운로드 API만 필터링
|
||||||
|
if (!request.getRequestURI().contains("/download")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = extractUserId(request);
|
||||||
|
String ip = ApiLogFunction.getClientIp(request);
|
||||||
|
|
||||||
|
List<?> list = menuService.getFindAll();
|
||||||
|
List<MenuDto.Basic> result =
|
||||||
|
list.stream()
|
||||||
|
.map(
|
||||||
|
item -> {
|
||||||
|
if (item instanceof LinkedHashMap<?, ?> map) {
|
||||||
|
return objectMapper.convertValue(map, MenuDto.Basic.class);
|
||||||
|
} else if (item instanceof MenuDto.Basic dto) {
|
||||||
|
return dto;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unsupported cache type: " + item.getClass());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
String normalizedUri = request.getRequestURI().replace("/api", "");
|
||||||
|
MenuDto.Basic basic =
|
||||||
|
result.stream()
|
||||||
|
.filter(
|
||||||
|
menu -> menu.getMenuUrl() != null && normalizedUri.startsWith(menu.getMenuUrl()))
|
||||||
|
.max(Comparator.comparingInt(m -> m.getMenuUrl().length()))
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
AuditLogEntity log =
|
||||||
|
AuditLogEntity.forFileDownload(
|
||||||
|
userId,
|
||||||
|
request.getRequestURI(),
|
||||||
|
Objects.requireNonNull(basic).getMenuUid(),
|
||||||
|
ip,
|
||||||
|
response.getStatus());
|
||||||
|
|
||||||
|
auditLogRepository.save(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long extractUserId(HttpServletRequest request) {
|
||||||
|
if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth
|
||||||
|
&& auth.getPrincipal() instanceof CustomUserDetails userDetails) {
|
||||||
|
return userDetails.getMember().getId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,11 +10,18 @@ import org.locationtech.jts.geom.Polygon;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class WebConfig implements WebMvcConfigurer {
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final FileDownloadInteceptor fileDownloadInteceptor;
|
||||||
|
|
||||||
|
public WebConfig(FileDownloadInteceptor fileDownloadInteceptor) {
|
||||||
|
this.fileDownloadInteceptor = fileDownloadInteceptor;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ObjectMapper objectMapper() {
|
public ObjectMapper objectMapper() {
|
||||||
SimpleModule module = new SimpleModule();
|
SimpleModule module = new SimpleModule();
|
||||||
@@ -29,4 +36,11 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
|
|
||||||
return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build();
|
return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry
|
||||||
|
.addInterceptor(fileDownloadInteceptor)
|
||||||
|
.addPathPatterns("/api/inference/download/**"); // 추론 파일 다운로드 API만 //TODO 추후 추가
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.log.dto.EventType;
|
|||||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -47,7 +48,7 @@ public class ApiLogFunction {
|
|||||||
String method = request.getMethod().toUpperCase();
|
String method = request.getMethod().toUpperCase();
|
||||||
String uri = request.getRequestURI().toLowerCase();
|
String uri = request.getRequestURI().toLowerCase();
|
||||||
|
|
||||||
// URL 기반 DOWNLOAD/PRINT 분류
|
// URL 기반 DOWNLOAD/PRINT 분류 -> /download는 FileDownloadInterceptor로 옮김
|
||||||
if (uri.contains("/download") || uri.contains("/export")) {
|
if (uri.contains("/download") || uri.contains("/export")) {
|
||||||
return EventType.DOWNLOAD;
|
return EventType.DOWNLOAD;
|
||||||
}
|
}
|
||||||
@@ -121,13 +122,15 @@ public class ApiLogFunction {
|
|||||||
|
|
||||||
public static String getUriMenuInfo(List<MenuDto.Basic> menuList, String uri) {
|
public static String getUriMenuInfo(List<MenuDto.Basic> menuList, String uri) {
|
||||||
|
|
||||||
MenuDto.Basic m =
|
String normalizedUri = uri.replace("/api", "");
|
||||||
|
MenuDto.Basic basic =
|
||||||
menuList.stream()
|
menuList.stream()
|
||||||
.filter(menu -> menu.getMenuApiUrl() != null && uri.contains(menu.getMenuApiUrl()))
|
.filter(
|
||||||
.findFirst()
|
menu -> menu.getMenuUrl() != null && normalizedUri.startsWith(menu.getMenuUrl()))
|
||||||
|
.max(Comparator.comparingInt(m -> m.getMenuUrl().length()))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
return m != null ? m.getMenuUid() : "SYSTEM";
|
return basic != null ? basic.getMenuUid() : "SYSTEM";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String cutRequestBody(String value) {
|
public static String cutRequestBody(String value) {
|
||||||
|
|||||||
@@ -4,7 +4,14 @@ import com.kamco.cd.kamcoback.log.dto.AuditLogDto;
|
|||||||
import com.kamco.cd.kamcoback.log.dto.EventStatus;
|
import com.kamco.cd.kamcoback.log.dto.EventStatus;
|
||||||
import com.kamco.cd.kamcoback.log.dto.EventType;
|
import com.kamco.cd.kamcoback.log.dto.EventType;
|
||||||
import com.kamco.cd.kamcoback.postgres.CommonCreateEntity;
|
import com.kamco.cd.kamcoback.postgres.CommonCreateEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -14,6 +21,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
@Table(name = "tb_audit_log")
|
@Table(name = "tb_audit_log")
|
||||||
public class AuditLogEntity extends CommonCreateEntity {
|
public class AuditLogEntity extends CommonCreateEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(name = "audit_log_uid", nullable = false)
|
@Column(name = "audit_log_uid", nullable = false)
|
||||||
@@ -62,6 +70,22 @@ public class AuditLogEntity extends CommonCreateEntity {
|
|||||||
this.errorLogUid = errorLogUid;
|
this.errorLogUid = errorLogUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 파일 다운로드 이력 생성 */
|
||||||
|
public static AuditLogEntity forFileDownload(
|
||||||
|
Long userId, String requestUri, String menuUid, String ip, int httpStatus) {
|
||||||
|
|
||||||
|
return new AuditLogEntity(
|
||||||
|
userId,
|
||||||
|
EventType.DOWNLOAD, // 이벤트 타입 고정
|
||||||
|
httpStatus < 400 ? EventStatus.FAILED : EventStatus.SUCCESS, // 성공 여부
|
||||||
|
menuUid,
|
||||||
|
ip,
|
||||||
|
requestUri,
|
||||||
|
null, // requestBody 없음
|
||||||
|
null // errorLogUid 없음
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public AuditLogDto.Basic toDto() {
|
public AuditLogDto.Basic toDto() {
|
||||||
return new AuditLogDto.Basic(
|
return new AuditLogDto.Basic(
|
||||||
this.id,
|
this.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user