Merge pull request '로그관리 로직 커밋' (#115) from feat/training_260202 into develop
Reviewed-on: #115
This commit was merged in pull request #115.
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
package com.kamco.cd.training.common.utils;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
public final class HeaderUtil {
|
||||||
|
|
||||||
|
private HeaderUtil() {}
|
||||||
|
|
||||||
|
/** 특정 Header 값 조회 */
|
||||||
|
public static String get(HttpServletRequest request, String headerName) {
|
||||||
|
if (request == null || headerName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = request.getHeader(headerName);
|
||||||
|
return (value != null && !value.isBlank()) ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 필수 Header 조회 (없으면 null) */
|
||||||
|
public static String getRequired(HttpServletRequest request, String headerName) {
|
||||||
|
return get(request, headerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,14 @@ import com.kamco.cd.training.log.dto.EventType;
|
|||||||
import com.kamco.cd.training.menu.dto.MenuDto;
|
import com.kamco.cd.training.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;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ApiLogFunction {
|
public class ApiLogFunction {
|
||||||
|
|
||||||
// 클라이언트 IP 추출
|
// 클라이언트 IP 추출
|
||||||
@@ -34,6 +37,14 @@ public class ApiLogFunction {
|
|||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getXFowardedForIp(HttpServletRequest request) {
|
||||||
|
String ip = request.getHeader("X-Forwarded-For");
|
||||||
|
if (ip != null) {
|
||||||
|
ip = ip.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
// 사용자 ID 추출 예시 (Spring Security 기준)
|
// 사용자 ID 추출 예시 (Spring Security 기준)
|
||||||
public static String getUserId(HttpServletRequest request) {
|
public static String getUserId(HttpServletRequest request) {
|
||||||
try {
|
try {
|
||||||
@@ -47,20 +58,20 @@ 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;
|
||||||
}
|
}
|
||||||
if (uri.contains("/print")) {
|
if (uri.contains("/print")) {
|
||||||
return EventType.PRINT;
|
return EventType.OTHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 일반 CRUD
|
// 일반 CRUD
|
||||||
return switch (method) {
|
return switch (method) {
|
||||||
case "POST" -> EventType.CREATE;
|
case "POST" -> EventType.ADDED;
|
||||||
case "GET" -> EventType.READ;
|
case "GET" -> EventType.LIST;
|
||||||
case "DELETE" -> EventType.DELETE;
|
case "DELETE" -> EventType.REMOVE;
|
||||||
case "PUT", "PATCH" -> EventType.UPDATE;
|
case "PUT", "PATCH" -> EventType.MODIFIED;
|
||||||
default -> EventType.OTHER;
|
default -> EventType.OTHER;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -121,12 +132,22 @@ 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) {
|
||||||
|
int MAX_LEN = 255;
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.length() <= MAX_LEN ? value : value.substring(0, MAX_LEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,17 @@ package com.kamco.cd.training.config.api;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.kamco.cd.training.auth.CustomUserDetails;
|
import com.kamco.cd.training.auth.CustomUserDetails;
|
||||||
|
import com.kamco.cd.training.common.utils.HeaderUtil;
|
||||||
|
import com.kamco.cd.training.log.dto.EventType;
|
||||||
|
import com.kamco.cd.training.menu.dto.MenuDto;
|
||||||
import com.kamco.cd.training.menu.service.MenuService;
|
import com.kamco.cd.training.menu.service.MenuService;
|
||||||
import com.kamco.cd.training.postgres.entity.AuditLogEntity;
|
import com.kamco.cd.training.postgres.entity.AuditLogEntity;
|
||||||
import com.kamco.cd.training.postgres.repository.log.AuditLogRepository;
|
import com.kamco.cd.training.postgres.repository.log.AuditLogRepository;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -23,6 +30,7 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
|
|||||||
*
|
*
|
||||||
* <p>createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT
|
* <p>createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||||
|
|
||||||
@@ -61,12 +69,27 @@ public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
|||||||
if (body instanceof ApiResponseDto<?> apiResponse) {
|
if (body instanceof ApiResponseDto<?> apiResponse) {
|
||||||
response.setStatusCode(apiResponse.getHttpStatus());
|
response.setStatusCode(apiResponse.getHttpStatus());
|
||||||
|
|
||||||
String ip = ApiLogFunction.getClientIp(servletRequest);
|
String actionType = HeaderUtil.get(servletRequest, "kamco-action-type");
|
||||||
Long userid = null;
|
// actionType 이 없으면 로그 저장하지 않기 || download 는 FileDownloadInterceptor 에서 하기
|
||||||
|
// (file down URL prefix 추가는 WebConfig.java 에 하기)
|
||||||
|
if (actionType == null || actionType.equalsIgnoreCase("download")) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
if (servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth
|
String ip =
|
||||||
&& auth.getPrincipal() instanceof CustomUserDetails customUserDetails) {
|
Optional.ofNullable(HeaderUtil.get(servletRequest, "kamco-user-ip"))
|
||||||
userid = customUserDetails.getMember().getId();
|
.orElseGet(() -> ApiLogFunction.getXFowardedForIp(servletRequest));
|
||||||
|
Long userid = null;
|
||||||
|
String loginAttemptId = null;
|
||||||
|
|
||||||
|
// 로그인 시도할 때
|
||||||
|
if (servletRequest.getRequestURI().contains("/api/auth/signin")) {
|
||||||
|
loginAttemptId = HeaderUtil.get(servletRequest, "kamco-login-attempt-id");
|
||||||
|
} else {
|
||||||
|
if (servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth
|
||||||
|
&& auth.getPrincipal() instanceof CustomUserDetails customUserDetails) {
|
||||||
|
userid = customUserDetails.getMember().getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String requestBody;
|
String requestBody;
|
||||||
@@ -84,17 +107,33 @@ public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
|||||||
requestBody = maskSensitiveFields(requestBody);
|
requestBody = maskSensitiveFields(requestBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
AuditLogEntity log =
|
AuditLogEntity log =
|
||||||
new AuditLogEntity(
|
new AuditLogEntity(
|
||||||
userid,
|
userid,
|
||||||
ApiLogFunction.getEventType(servletRequest),
|
EventType.fromName(actionType),
|
||||||
ApiLogFunction.isSuccessFail(apiResponse),
|
ApiLogFunction.isSuccessFail(apiResponse),
|
||||||
ApiLogFunction.getUriMenuInfo(
|
ApiLogFunction.getUriMenuInfo(result, servletRequest.getRequestURI()),
|
||||||
menuService.getFindAll(), servletRequest.getRequestURI()),
|
|
||||||
ip,
|
ip,
|
||||||
servletRequest.getRequestURI(),
|
servletRequest.getRequestURI(),
|
||||||
requestBody,
|
ApiLogFunction.cutRequestBody(requestBody),
|
||||||
apiResponse.getErrorLogUid());
|
apiResponse.getErrorLogUid(),
|
||||||
|
null,
|
||||||
|
loginAttemptId);
|
||||||
auditLogRepository.save(log);
|
auditLogRepository.save(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.kamco.cd.training.log;
|
||||||
|
|
||||||
|
import com.kamco.cd.training.config.api.ApiResponseDto;
|
||||||
|
import com.kamco.cd.training.log.dto.AuditLogDto;
|
||||||
|
import com.kamco.cd.training.log.service.AuditLogService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "감사 로그", description = "감사 로그 관리 API")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/logs/audit")
|
||||||
|
public class AuditLogApiController {
|
||||||
|
|
||||||
|
private final AuditLogService auditLogService;
|
||||||
|
|
||||||
|
@Operation(summary = "일자별 로그 조회")
|
||||||
|
@GetMapping("/daily")
|
||||||
|
public ApiResponseDto<Page<AuditLogDto.DailyAuditList>> getDailyLogs(
|
||||||
|
@RequestParam(required = false) LocalDate startDate,
|
||||||
|
@RequestParam(required = false) LocalDate endDate,
|
||||||
|
@RequestParam int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
|
AuditLogDto.searchReq searchReq = new AuditLogDto.searchReq(page, size, "created_dttm,desc");
|
||||||
|
|
||||||
|
Page<AuditLogDto.DailyAuditList> result =
|
||||||
|
auditLogService.getLogByDaily(searchReq, startDate, endDate);
|
||||||
|
|
||||||
|
return ApiResponseDto.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "일자별 로그 상세")
|
||||||
|
@GetMapping("/daily/result")
|
||||||
|
public ApiResponseDto<Page<AuditLogDto.DailyDetail>> getDailyResultLogs(
|
||||||
|
@RequestParam LocalDate logDate,
|
||||||
|
@RequestParam int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
|
AuditLogDto.searchReq searchReq = new AuditLogDto.searchReq(page, size, "created_dttm,desc");
|
||||||
|
Page<AuditLogDto.DailyDetail> result = auditLogService.getLogByDailyResult(searchReq, logDate);
|
||||||
|
|
||||||
|
return ApiResponseDto.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "메뉴별 로그 조회")
|
||||||
|
@GetMapping("/menu")
|
||||||
|
public ApiResponseDto<Page<AuditLogDto.MenuAuditList>> getMenuLogs(
|
||||||
|
@RequestParam(required = false) String searchValue,
|
||||||
|
@RequestParam int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
|
AuditLogDto.searchReq searchReq = new AuditLogDto.searchReq(page, size, "created_dttm,desc");
|
||||||
|
Page<AuditLogDto.MenuAuditList> result = auditLogService.getLogByMenu(searchReq, searchValue);
|
||||||
|
|
||||||
|
return ApiResponseDto.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "메뉴별 로그 상세")
|
||||||
|
@GetMapping("/menu/result")
|
||||||
|
public ApiResponseDto<Page<AuditLogDto.MenuDetail>> getMenuResultLogs(
|
||||||
|
@RequestParam String menuId,
|
||||||
|
@RequestParam int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
|
AuditLogDto.searchReq searchReq = new AuditLogDto.searchReq(page, size, "created_dttm,desc");
|
||||||
|
Page<AuditLogDto.MenuDetail> result = auditLogService.getLogByMenuResult(searchReq, menuId);
|
||||||
|
|
||||||
|
return ApiResponseDto.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "사용자별 로그 조회")
|
||||||
|
@GetMapping("/account")
|
||||||
|
public ApiResponseDto<Page<AuditLogDto.UserAuditList>> getAccountLogs(
|
||||||
|
@RequestParam(required = false) String searchValue,
|
||||||
|
@RequestParam int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
|
AuditLogDto.searchReq searchReq = new AuditLogDto.searchReq(page, size, "created_dttm,desc");
|
||||||
|
Page<AuditLogDto.UserAuditList> result =
|
||||||
|
auditLogService.getLogByAccount(searchReq, searchValue);
|
||||||
|
|
||||||
|
return ApiResponseDto.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "사용자별 로그 상세")
|
||||||
|
@GetMapping("/account/result")
|
||||||
|
public ApiResponseDto<Page<AuditLogDto.UserDetail>> getAccountResultLogs(
|
||||||
|
@RequestParam Long userUid,
|
||||||
|
@RequestParam int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
|
AuditLogDto.searchReq searchReq = new AuditLogDto.searchReq(page, size, "created_dttm,desc");
|
||||||
|
Page<AuditLogDto.UserDetail> result = auditLogService.getLogByAccountResult(searchReq, userUid);
|
||||||
|
|
||||||
|
return ApiResponseDto.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.kamco.cd.training.log;
|
||||||
|
|
||||||
|
import com.kamco.cd.training.config.api.ApiResponseDto;
|
||||||
|
import com.kamco.cd.training.log.dto.ErrorLogDto;
|
||||||
|
import com.kamco.cd.training.log.dto.EventType;
|
||||||
|
import com.kamco.cd.training.log.service.ErrorLogService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "에러 로그", description = "에러 로그 관리 API")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping({"/api/logs/system"})
|
||||||
|
public class ErrorLogApiController {
|
||||||
|
|
||||||
|
private final ErrorLogService errorLogService;
|
||||||
|
|
||||||
|
@Operation(summary = "에러로그 조회")
|
||||||
|
@GetMapping("/error")
|
||||||
|
public ApiResponseDto<Page<ErrorLogDto.Basic>> getErrorLogs(
|
||||||
|
@RequestParam(required = false) ErrorLogDto.LogErrorLevel logErrorLevel,
|
||||||
|
@RequestParam(required = false) EventType eventType,
|
||||||
|
@RequestParam(required = false) LocalDate startDate,
|
||||||
|
@RequestParam(required = false) LocalDate endDate,
|
||||||
|
@RequestParam int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
|
ErrorLogDto.ErrorSearchReq searchReq =
|
||||||
|
new ErrorLogDto.ErrorSearchReq(
|
||||||
|
logErrorLevel, eventType, startDate, endDate, page, size, "created_dttm,desc");
|
||||||
|
Page<ErrorLogDto.Basic> result = errorLogService.findLogByError(searchReq);
|
||||||
|
return ApiResponseDto.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@ package com.kamco.cd.training.log.dto;
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
|
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -58,6 +60,7 @@ public class AuditLogDto {
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class AuditCommon {
|
public static class AuditCommon {
|
||||||
|
|
||||||
private int readCount;
|
private int readCount;
|
||||||
private int cudCount;
|
private int cudCount;
|
||||||
private int printCount;
|
private int printCount;
|
||||||
@@ -68,6 +71,7 @@ public class AuditLogDto {
|
|||||||
@Schema(name = "DailyAuditList", description = "일자별 목록")
|
@Schema(name = "DailyAuditList", description = "일자별 목록")
|
||||||
@Getter
|
@Getter
|
||||||
public static class DailyAuditList extends AuditCommon {
|
public static class DailyAuditList extends AuditCommon {
|
||||||
|
|
||||||
private final String baseDate;
|
private final String baseDate;
|
||||||
|
|
||||||
public DailyAuditList(
|
public DailyAuditList(
|
||||||
@@ -85,6 +89,7 @@ public class AuditLogDto {
|
|||||||
@Schema(name = "MenuAuditList", description = "메뉴별 목록")
|
@Schema(name = "MenuAuditList", description = "메뉴별 목록")
|
||||||
@Getter
|
@Getter
|
||||||
public static class MenuAuditList extends AuditCommon {
|
public static class MenuAuditList extends AuditCommon {
|
||||||
|
|
||||||
private final String menuId;
|
private final String menuId;
|
||||||
private final String menuName;
|
private final String menuName;
|
||||||
|
|
||||||
@@ -105,6 +110,7 @@ public class AuditLogDto {
|
|||||||
@Schema(name = "UserAuditList", description = "사용자별 목록")
|
@Schema(name = "UserAuditList", description = "사용자별 목록")
|
||||||
@Getter
|
@Getter
|
||||||
public static class UserAuditList extends AuditCommon {
|
public static class UserAuditList extends AuditCommon {
|
||||||
|
|
||||||
private final Long accountId;
|
private final Long accountId;
|
||||||
private final String loginId;
|
private final String loginId;
|
||||||
private final String username;
|
private final String username;
|
||||||
@@ -129,6 +135,7 @@ public class AuditLogDto {
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class AuditDetail {
|
public static class AuditDetail {
|
||||||
|
|
||||||
private Long logId;
|
private Long logId;
|
||||||
private EventType eventType;
|
private EventType eventType;
|
||||||
private LogDetail detail;
|
private LogDetail detail;
|
||||||
@@ -137,9 +144,11 @@ public class AuditLogDto {
|
|||||||
@Schema(name = "DailyDetail", description = "일자별 로그 상세")
|
@Schema(name = "DailyDetail", description = "일자별 로그 상세")
|
||||||
@Getter
|
@Getter
|
||||||
public static class DailyDetail extends AuditDetail {
|
public static class DailyDetail extends AuditDetail {
|
||||||
|
|
||||||
private final String userName;
|
private final String userName;
|
||||||
private final String loginId;
|
private final String loginId;
|
||||||
private final String menuName;
|
private final String menuName;
|
||||||
|
private final String logDateTime;
|
||||||
|
|
||||||
public DailyDetail(
|
public DailyDetail(
|
||||||
Long logId,
|
Long logId,
|
||||||
@@ -147,17 +156,20 @@ public class AuditLogDto {
|
|||||||
String loginId,
|
String loginId,
|
||||||
String menuName,
|
String menuName,
|
||||||
EventType eventType,
|
EventType eventType,
|
||||||
|
String logDateTime,
|
||||||
LogDetail detail) {
|
LogDetail detail) {
|
||||||
super(logId, eventType, detail);
|
super(logId, eventType, detail);
|
||||||
this.userName = userName;
|
this.userName = userName;
|
||||||
this.loginId = loginId;
|
this.loginId = loginId;
|
||||||
this.menuName = menuName;
|
this.menuName = menuName;
|
||||||
|
this.logDateTime = logDateTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(name = "MenuDetail", description = "메뉴별 로그 상세")
|
@Schema(name = "MenuDetail", description = "메뉴별 로그 상세")
|
||||||
@Getter
|
@Getter
|
||||||
public static class MenuDetail extends AuditDetail {
|
public static class MenuDetail extends AuditDetail {
|
||||||
|
|
||||||
private final String logDateTime;
|
private final String logDateTime;
|
||||||
private final String userName;
|
private final String userName;
|
||||||
private final String loginId;
|
private final String loginId;
|
||||||
@@ -179,6 +191,7 @@ public class AuditLogDto {
|
|||||||
@Schema(name = "UserDetail", description = "사용자별 로그 상세")
|
@Schema(name = "UserDetail", description = "사용자별 로그 상세")
|
||||||
@Getter
|
@Getter
|
||||||
public static class UserDetail extends AuditDetail {
|
public static class UserDetail extends AuditDetail {
|
||||||
|
|
||||||
private final String logDateTime;
|
private final String logDateTime;
|
||||||
private final String menuNm;
|
private final String menuNm;
|
||||||
|
|
||||||
@@ -194,6 +207,7 @@ public class AuditLogDto {
|
|||||||
@Setter
|
@Setter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class LogDetail {
|
public static class LogDetail {
|
||||||
|
|
||||||
String serviceName;
|
String serviceName;
|
||||||
String parentMenuName;
|
String parentMenuName;
|
||||||
String menuName;
|
String menuName;
|
||||||
@@ -226,4 +240,26 @@ public class AuditLogDto {
|
|||||||
return PageRequest.of(page, size);
|
return PageRequest.of(page, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class DownloadReq {
|
||||||
|
|
||||||
|
UUID uuid;
|
||||||
|
LocalDate startDate;
|
||||||
|
LocalDate endDate;
|
||||||
|
String searchValue;
|
||||||
|
String menuId;
|
||||||
|
String requestUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class DownloadRes {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
String employeeNo;
|
||||||
|
@JsonFormatDttm ZonedDateTime downloadDttm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
package com.kamco.cd.training.log.dto;
|
package com.kamco.cd.training.log.dto;
|
||||||
|
|
||||||
|
import com.kamco.cd.training.common.utils.enums.CodeExpose;
|
||||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@CodeExpose
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum EventType implements EnumType {
|
public enum EventType implements EnumType {
|
||||||
CREATE("생성"),
|
LIST("목록"),
|
||||||
READ("조회"),
|
DETAIL("상세"),
|
||||||
UPDATE("수정"),
|
POPUP("팝업"),
|
||||||
DELETE("삭제"),
|
STATUS("상태"),
|
||||||
|
ADDED("추가"),
|
||||||
|
MODIFIED("수정"),
|
||||||
|
REMOVE("삭제"),
|
||||||
DOWNLOAD("다운로드"),
|
DOWNLOAD("다운로드"),
|
||||||
PRINT("출력"),
|
LOGIN("로그인"),
|
||||||
OTHER("기타");
|
OTHER("기타");
|
||||||
|
|
||||||
private final String desc;
|
private final String desc;
|
||||||
|
|
||||||
|
public static EventType fromName(String name) {
|
||||||
|
try {
|
||||||
|
return EventType.valueOf(name.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return OTHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return name();
|
return name();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.kamco.cd.training.postgres.core;
|
|||||||
|
|
||||||
import com.kamco.cd.training.common.service.BaseCoreService;
|
import com.kamco.cd.training.common.service.BaseCoreService;
|
||||||
import com.kamco.cd.training.log.dto.AuditLogDto;
|
import com.kamco.cd.training.log.dto.AuditLogDto;
|
||||||
|
import com.kamco.cd.training.log.dto.AuditLogDto.DownloadReq;
|
||||||
import com.kamco.cd.training.postgres.repository.log.AuditLogRepository;
|
import com.kamco.cd.training.postgres.repository.log.AuditLogRepository;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -45,6 +46,11 @@ public class AuditLogCoreService
|
|||||||
return auditLogRepository.findLogByAccount(searchRange, searchValue);
|
return auditLogRepository.findLogByAccount(searchRange, searchValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Page<AuditLogDto.DownloadRes> findLogByAccount(
|
||||||
|
AuditLogDto.searchReq searchReq, DownloadReq downloadReq) {
|
||||||
|
return auditLogRepository.findDownloadLog(searchReq, downloadReq);
|
||||||
|
}
|
||||||
|
|
||||||
public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
|
public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
|
||||||
AuditLogDto.searchReq searchRange, LocalDate logDate) {
|
AuditLogDto.searchReq searchRange, LocalDate logDate) {
|
||||||
return auditLogRepository.findLogByDailyResult(searchRange, logDate);
|
return auditLogRepository.findLogByDailyResult(searchRange, logDate);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.kamco.cd.training.log.dto.EventStatus;
|
|||||||
import com.kamco.cd.training.log.dto.EventType;
|
import com.kamco.cd.training.log.dto.EventType;
|
||||||
import com.kamco.cd.training.postgres.CommonCreateEntity;
|
import com.kamco.cd.training.postgres.CommonCreateEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import java.util.UUID;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -14,6 +15,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)
|
||||||
@@ -43,6 +45,12 @@ public class AuditLogEntity extends CommonCreateEntity {
|
|||||||
@Column(name = "error_log_uid")
|
@Column(name = "error_log_uid")
|
||||||
private Long errorLogUid;
|
private Long errorLogUid;
|
||||||
|
|
||||||
|
@Column(name = "download_uuid")
|
||||||
|
private UUID downloadUuid;
|
||||||
|
|
||||||
|
@Column(name = "login_attempt_id")
|
||||||
|
private String loginAttemptId;
|
||||||
|
|
||||||
public AuditLogEntity(
|
public AuditLogEntity(
|
||||||
Long userUid,
|
Long userUid,
|
||||||
EventType eventType,
|
EventType eventType,
|
||||||
@@ -51,7 +59,9 @@ public class AuditLogEntity extends CommonCreateEntity {
|
|||||||
String ipAddress,
|
String ipAddress,
|
||||||
String requestUri,
|
String requestUri,
|
||||||
String requestBody,
|
String requestBody,
|
||||||
Long errorLogUid) {
|
Long errorLogUid,
|
||||||
|
UUID downloadUuid,
|
||||||
|
String loginAttemptId) {
|
||||||
this.userUid = userUid;
|
this.userUid = userUid;
|
||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
this.eventStatus = eventStatus;
|
this.eventStatus = eventStatus;
|
||||||
@@ -60,6 +70,31 @@ public class AuditLogEntity extends CommonCreateEntity {
|
|||||||
this.requestUri = requestUri;
|
this.requestUri = requestUri;
|
||||||
this.requestBody = requestBody;
|
this.requestBody = requestBody;
|
||||||
this.errorLogUid = errorLogUid;
|
this.errorLogUid = errorLogUid;
|
||||||
|
this.downloadUuid = downloadUuid;
|
||||||
|
this.loginAttemptId = loginAttemptId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 파일 다운로드 이력 생성 */
|
||||||
|
public static AuditLogEntity forFileDownload(
|
||||||
|
Long userId,
|
||||||
|
String requestUri,
|
||||||
|
String menuUid,
|
||||||
|
String ip,
|
||||||
|
int httpStatus,
|
||||||
|
UUID downloadUuid) {
|
||||||
|
|
||||||
|
return new AuditLogEntity(
|
||||||
|
userId,
|
||||||
|
EventType.DOWNLOAD, // 이벤트 타입 고정
|
||||||
|
httpStatus < 400 ? EventStatus.SUCCESS : EventStatus.FAILED, // 성공 여부
|
||||||
|
menuUid,
|
||||||
|
ip,
|
||||||
|
requestUri,
|
||||||
|
null, // requestBody 없음
|
||||||
|
null, // errorLogUid 없음
|
||||||
|
downloadUuid,
|
||||||
|
null // loginAttemptId 없음
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuditLogDto.Basic toDto() {
|
public AuditLogDto.Basic toDto() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.kamco.cd.training.postgres.repository.log;
|
package com.kamco.cd.training.postgres.repository.log;
|
||||||
|
|
||||||
import com.kamco.cd.training.log.dto.AuditLogDto;
|
import com.kamco.cd.training.log.dto.AuditLogDto;
|
||||||
|
import com.kamco.cd.training.log.dto.AuditLogDto.DownloadReq;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
|
|
||||||
@@ -15,6 +16,9 @@ public interface AuditLogRepositoryCustom {
|
|||||||
Page<AuditLogDto.UserAuditList> findLogByAccount(
|
Page<AuditLogDto.UserAuditList> findLogByAccount(
|
||||||
AuditLogDto.searchReq searchReq, String searchValue);
|
AuditLogDto.searchReq searchReq, String searchValue);
|
||||||
|
|
||||||
|
Page<AuditLogDto.DownloadRes> findDownloadLog(
|
||||||
|
AuditLogDto.searchReq searchReq, DownloadReq downloadReq);
|
||||||
|
|
||||||
Page<AuditLogDto.DailyDetail> findLogByDailyResult(
|
Page<AuditLogDto.DailyDetail> findLogByDailyResult(
|
||||||
AuditLogDto.searchReq searchReq, LocalDate logDate);
|
AuditLogDto.searchReq searchReq, LocalDate logDate);
|
||||||
|
|
||||||
|
|||||||
@@ -6,32 +6,42 @@ import static com.kamco.cd.training.postgres.entity.QMemberEntity.memberEntity;
|
|||||||
import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity;
|
import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity;
|
||||||
|
|
||||||
import com.kamco.cd.training.log.dto.AuditLogDto;
|
import com.kamco.cd.training.log.dto.AuditLogDto;
|
||||||
|
import com.kamco.cd.training.log.dto.AuditLogDto.DownloadReq;
|
||||||
|
import com.kamco.cd.training.log.dto.AuditLogDto.searchReq;
|
||||||
import com.kamco.cd.training.log.dto.ErrorLogDto;
|
import com.kamco.cd.training.log.dto.ErrorLogDto;
|
||||||
import com.kamco.cd.training.log.dto.EventStatus;
|
import com.kamco.cd.training.log.dto.EventStatus;
|
||||||
import com.kamco.cd.training.log.dto.EventType;
|
import com.kamco.cd.training.log.dto.EventType;
|
||||||
|
import com.kamco.cd.training.postgres.entity.AuditLogEntity;
|
||||||
import com.kamco.cd.training.postgres.entity.QMenuEntity;
|
import com.kamco.cd.training.postgres.entity.QMenuEntity;
|
||||||
|
import com.querydsl.core.BooleanBuilder;
|
||||||
import com.querydsl.core.types.Projections;
|
import com.querydsl.core.types.Projections;
|
||||||
import com.querydsl.core.types.dsl.*;
|
import com.querydsl.core.types.dsl.*;
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
import io.micrometer.common.util.StringUtils;
|
import io.micrometer.common.util.StringUtils;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageImpl;
|
import org.springframework.data.domain.PageImpl;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@RequiredArgsConstructor
|
public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||||
public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
implements AuditLogRepositoryCustom {
|
||||||
|
|
||||||
|
private static final ZoneId ZONE = ZoneId.of("Asia/Seoul");
|
||||||
private final JPAQueryFactory queryFactory;
|
private final JPAQueryFactory queryFactory;
|
||||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||||
|
|
||||||
|
public AuditLogRepositoryImpl(JPAQueryFactory queryFactory) {
|
||||||
|
super(AuditLogEntity.class);
|
||||||
|
this.queryFactory = queryFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<AuditLogDto.DailyAuditList> findLogByDaily(
|
public Page<AuditLogDto.DailyAuditList> findLogByDaily(
|
||||||
AuditLogDto.searchReq searchReq, LocalDate startDate, LocalDate endDate) {
|
AuditLogDto.searchReq searchReq, LocalDate startDate, LocalDate endDate) {
|
||||||
@@ -87,7 +97,7 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
.from(auditLogEntity)
|
.from(auditLogEntity)
|
||||||
.leftJoin(menuEntity)
|
.leftJoin(menuEntity)
|
||||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||||
.where(menuNameEquals(searchValue))
|
.where(auditLogEntity.menuUid.ne("SYSTEM"), menuNameEquals(searchValue))
|
||||||
.groupBy(auditLogEntity.menuUid)
|
.groupBy(auditLogEntity.menuUid)
|
||||||
.offset(pageable.getOffset())
|
.offset(pageable.getOffset())
|
||||||
.limit(pageable.getPageSize())
|
.limit(pageable.getPageSize())
|
||||||
@@ -128,7 +138,7 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
.from(auditLogEntity)
|
.from(auditLogEntity)
|
||||||
.leftJoin(memberEntity)
|
.leftJoin(memberEntity)
|
||||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||||
.where(loginIdOrUsernameContains(searchValue))
|
.where(auditLogEntity.userUid.isNotNull(), loginIdOrUsernameContains(searchValue))
|
||||||
.groupBy(auditLogEntity.userUid, memberEntity.employeeNo, memberEntity.name)
|
.groupBy(auditLogEntity.userUid, memberEntity.employeeNo, memberEntity.name)
|
||||||
.offset(pageable.getOffset())
|
.offset(pageable.getOffset())
|
||||||
.limit(pageable.getPageSize())
|
.limit(pageable.getPageSize())
|
||||||
@@ -147,6 +157,62 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
return new PageImpl<>(foundContent, pageable, countQuery);
|
return new PageImpl<>(foundContent, pageable, countQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<AuditLogDto.DownloadRes> findDownloadLog(
|
||||||
|
AuditLogDto.searchReq searchReq, DownloadReq req) {
|
||||||
|
Pageable pageable = searchReq.toPageable();
|
||||||
|
|
||||||
|
BooleanBuilder whereBuilder = new BooleanBuilder();
|
||||||
|
|
||||||
|
whereBuilder.and(auditLogEntity.eventStatus.ne(EventStatus.valueOf("FAILED")));
|
||||||
|
whereBuilder.and(auditLogEntity.eventType.eq(EventType.valueOf("DOWNLOAD")));
|
||||||
|
|
||||||
|
// if (req.getMenuId() != null && !req.getMenuId().isEmpty()) {
|
||||||
|
// whereBuilder.and(auditLogEntity.menuUid.eq(req.getMenuId()));
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (req.getUuid() != null) {
|
||||||
|
whereBuilder.and(auditLogEntity.requestUri.contains(req.getRequestUri()));
|
||||||
|
whereBuilder.and(auditLogEntity.downloadUuid.eq(req.getUuid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.getSearchValue() != null && !req.getSearchValue().isEmpty()) {
|
||||||
|
whereBuilder.and(
|
||||||
|
memberEntity
|
||||||
|
.name
|
||||||
|
.contains(req.getSearchValue())
|
||||||
|
.or(memberEntity.employeeNo.contains(req.getSearchValue())));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AuditLogDto.DownloadRes> foundContent =
|
||||||
|
queryFactory
|
||||||
|
.select(
|
||||||
|
Projections.constructor(
|
||||||
|
AuditLogDto.DownloadRes.class,
|
||||||
|
memberEntity.name,
|
||||||
|
memberEntity.employeeNo,
|
||||||
|
auditLogEntity.createdDate.as("downloadDttm")))
|
||||||
|
.from(auditLogEntity)
|
||||||
|
.leftJoin(memberEntity)
|
||||||
|
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||||
|
.where(whereBuilder, createdDateBetween(req.getStartDate(), req.getEndDate()))
|
||||||
|
.offset(pageable.getOffset())
|
||||||
|
.limit(pageable.getPageSize())
|
||||||
|
.orderBy(auditLogEntity.createdDate.desc())
|
||||||
|
.fetch();
|
||||||
|
|
||||||
|
Long countQuery =
|
||||||
|
queryFactory
|
||||||
|
.select(auditLogEntity.userUid.countDistinct())
|
||||||
|
.from(auditLogEntity)
|
||||||
|
.leftJoin(memberEntity)
|
||||||
|
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||||
|
.where(whereBuilder, createdDateBetween(req.getStartDate(), req.getEndDate()))
|
||||||
|
.fetchOne();
|
||||||
|
|
||||||
|
return new PageImpl<>(foundContent, pageable, countQuery);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<AuditLogDto.DailyDetail> findLogByDailyResult(
|
public Page<AuditLogDto.DailyDetail> findLogByDailyResult(
|
||||||
AuditLogDto.searchReq searchReq, LocalDate logDate) {
|
AuditLogDto.searchReq searchReq, LocalDate logDate) {
|
||||||
@@ -176,6 +242,9 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
memberEntity.employeeNo.as("loginId"),
|
memberEntity.employeeNo.as("loginId"),
|
||||||
menuEntity.menuNm.as("menuName"),
|
menuEntity.menuNm.as("menuName"),
|
||||||
auditLogEntity.eventType.as("eventType"),
|
auditLogEntity.eventType.as("eventType"),
|
||||||
|
Expressions.stringTemplate(
|
||||||
|
"to_char({0}, 'YYYY-MM-DD HH24:MI')", auditLogEntity.createdDate)
|
||||||
|
.as("logDateTime"),
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
AuditLogDto.LogDetail.class,
|
AuditLogDto.LogDetail.class,
|
||||||
Expressions.constant("한국자산관리공사"), // serviceName
|
Expressions.constant("한국자산관리공사"), // serviceName
|
||||||
@@ -184,7 +253,7 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
menuEntity.menuUrl.as("menuUrl"),
|
menuEntity.menuUrl.as("menuUrl"),
|
||||||
menuEntity.description.as("menuDescription"),
|
menuEntity.description.as("menuDescription"),
|
||||||
menuEntity.menuOrder.as("sortOrder"),
|
menuEntity.menuOrder.as("sortOrder"),
|
||||||
menuEntity.isUse.as("used"))))
|
menuEntity.isUse.as("used")))) // TODO
|
||||||
.from(auditLogEntity)
|
.from(auditLogEntity)
|
||||||
.leftJoin(menuEntity)
|
.leftJoin(menuEntity)
|
||||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||||
@@ -238,8 +307,8 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
AuditLogDto.MenuDetail.class,
|
AuditLogDto.MenuDetail.class,
|
||||||
auditLogEntity.id.as("logId"),
|
auditLogEntity.id.as("logId"),
|
||||||
Expressions.stringTemplate(
|
Expressions.stringTemplate(
|
||||||
"to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate)
|
"to_char({0}, 'YYYY-MM-DD HH24:MI')", auditLogEntity.createdDate)
|
||||||
.as("logDateTime"), // ??
|
.as("logDateTime"),
|
||||||
memberEntity.name.as("userName"),
|
memberEntity.name.as("userName"),
|
||||||
memberEntity.employeeNo.as("loginId"),
|
memberEntity.employeeNo.as("loginId"),
|
||||||
auditLogEntity.eventType.as("eventType"),
|
auditLogEntity.eventType.as("eventType"),
|
||||||
@@ -305,7 +374,7 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
AuditLogDto.UserDetail.class,
|
AuditLogDto.UserDetail.class,
|
||||||
auditLogEntity.id.as("logId"),
|
auditLogEntity.id.as("logId"),
|
||||||
Expressions.stringTemplate(
|
Expressions.stringTemplate(
|
||||||
"to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate)
|
"to_char({0}, 'YYYY-MM-DD HH24:MI')", auditLogEntity.createdDate)
|
||||||
.as("logDateTime"),
|
.as("logDateTime"),
|
||||||
menuEntity.menuNm.as("menuName"),
|
menuEntity.menuNm.as("menuName"),
|
||||||
auditLogEntity.eventType.as("eventType"),
|
auditLogEntity.eventType.as("eventType"),
|
||||||
@@ -349,12 +418,23 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
|
if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
LocalDateTime startDateTime = startDate.atStartOfDay();
|
ZoneId zoneId = ZoneId.of("Asia/Seoul");
|
||||||
LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();
|
ZonedDateTime startDateTime = startDate.atStartOfDay(zoneId);
|
||||||
|
ZonedDateTime endDateTime = endDate.plusDays(1).atStartOfDay(zoneId);
|
||||||
return auditLogEntity
|
return auditLogEntity
|
||||||
.createdDate
|
.createdDate
|
||||||
.goe(ZonedDateTime.from(startDateTime))
|
.goe(startDateTime)
|
||||||
.and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime)));
|
.and(auditLogEntity.createdDate.lt(endDateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BooleanExpression createdDateBetween(LocalDate startDate, LocalDate endDate) {
|
||||||
|
if (startDate == null || endDate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ZonedDateTime start = startDate.atStartOfDay(ZONE);
|
||||||
|
ZonedDateTime endExclusive = endDate.plusDays(1).atStartOfDay(ZONE);
|
||||||
|
|
||||||
|
return auditLogEntity.createdDate.goe(start).and(auditLogEntity.createdDate.lt(endExclusive));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BooleanExpression menuNameEquals(String searchValue) {
|
private BooleanExpression menuNameEquals(String searchValue) {
|
||||||
@@ -393,11 +473,11 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BooleanExpression eventEndedAtEqDate(LocalDate logDate) {
|
private BooleanExpression eventEndedAtEqDate(LocalDate logDate) {
|
||||||
StringExpression eventEndedDate =
|
ZoneId zoneId = ZoneId.of("Asia/Seoul");
|
||||||
Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate);
|
ZonedDateTime start = logDate.atStartOfDay(zoneId);
|
||||||
LocalDateTime comparisonDate = logDate.atStartOfDay();
|
ZonedDateTime end = logDate.plusDays(1).atStartOfDay(zoneId);
|
||||||
|
|
||||||
return eventEndedDate.eq(comparisonDate.toString());
|
return auditLogEntity.createdDate.goe(start).and(auditLogEntity.createdDate.lt(end));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BooleanExpression menuUidEq(String menuUid) {
|
private BooleanExpression menuUidEq(String menuUid) {
|
||||||
@@ -410,7 +490,7 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
|
|
||||||
private NumberExpression<Integer> readCount() {
|
private NumberExpression<Integer> readCount() {
|
||||||
return new CaseBuilder()
|
return new CaseBuilder()
|
||||||
.when(auditLogEntity.eventType.eq(EventType.READ))
|
.when(auditLogEntity.eventType.in(EventType.LIST, EventType.DETAIL))
|
||||||
.then(1)
|
.then(1)
|
||||||
.otherwise(0)
|
.otherwise(0)
|
||||||
.sum();
|
.sum();
|
||||||
@@ -418,7 +498,7 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
|
|
||||||
private NumberExpression<Integer> cudCount() {
|
private NumberExpression<Integer> cudCount() {
|
||||||
return new CaseBuilder()
|
return new CaseBuilder()
|
||||||
.when(auditLogEntity.eventType.in(EventType.CREATE, EventType.UPDATE, EventType.DELETE))
|
.when(auditLogEntity.eventType.in(EventType.ADDED, EventType.MODIFIED, EventType.REMOVE))
|
||||||
.then(1)
|
.then(1)
|
||||||
.otherwise(0)
|
.otherwise(0)
|
||||||
.sum();
|
.sum();
|
||||||
@@ -426,7 +506,7 @@ public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom {
|
|||||||
|
|
||||||
private NumberExpression<Integer> printCount() {
|
private NumberExpression<Integer> printCount() {
|
||||||
return new CaseBuilder()
|
return new CaseBuilder()
|
||||||
.when(auditLogEntity.eventType.eq(EventType.PRINT))
|
.when(auditLogEntity.eventType.eq(EventType.OTHER))
|
||||||
.then(1)
|
.then(1)
|
||||||
.otherwise(0)
|
.otherwise(0)
|
||||||
.sum();
|
.sum();
|
||||||
|
|||||||
@@ -8,29 +8,35 @@ import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity;
|
|||||||
import com.kamco.cd.training.log.dto.ErrorLogDto;
|
import com.kamco.cd.training.log.dto.ErrorLogDto;
|
||||||
import com.kamco.cd.training.log.dto.EventStatus;
|
import com.kamco.cd.training.log.dto.EventStatus;
|
||||||
import com.kamco.cd.training.log.dto.EventType;
|
import com.kamco.cd.training.log.dto.EventType;
|
||||||
|
import com.kamco.cd.training.postgres.entity.AuditLogEntity;
|
||||||
import com.querydsl.core.types.Projections;
|
import com.querydsl.core.types.Projections;
|
||||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||||
import com.querydsl.core.types.dsl.Expressions;
|
import com.querydsl.core.types.dsl.Expressions;
|
||||||
import com.querydsl.core.types.dsl.StringExpression;
|
import com.querydsl.core.types.dsl.StringExpression;
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageImpl;
|
import org.springframework.data.domain.PageImpl;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@RequiredArgsConstructor
|
public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport
|
||||||
public class ErrorLogRepositoryImpl implements ErrorLogRepositoryCustom {
|
implements ErrorLogRepositoryCustom {
|
||||||
|
|
||||||
private final JPAQueryFactory queryFactory;
|
private final JPAQueryFactory queryFactory;
|
||||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||||
|
|
||||||
|
public ErrorLogRepositoryImpl(JPAQueryFactory queryFactory) {
|
||||||
|
super(AuditLogEntity.class);
|
||||||
|
this.queryFactory = queryFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) {
|
public Page<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) {
|
||||||
Pageable pageable = searchReq.toPageable();
|
Pageable pageable = searchReq.toPageable();
|
||||||
@@ -52,7 +58,7 @@ public class ErrorLogRepositoryImpl implements ErrorLogRepositoryCustom {
|
|||||||
errorLogEntity.errorMessage.as("errorMessage"),
|
errorLogEntity.errorMessage.as("errorMessage"),
|
||||||
errorLogEntity.stackTrace.as("errorDetail"),
|
errorLogEntity.stackTrace.as("errorDetail"),
|
||||||
Expressions.stringTemplate(
|
Expressions.stringTemplate(
|
||||||
"to_char({0}, 'YYYY-MM-DD')", errorLogEntity.createdDate)))
|
"to_char({0}, 'YYYY-MM-DD HH24:MI:SS.FF3')", errorLogEntity.createdDate)))
|
||||||
.from(errorLogEntity)
|
.from(errorLogEntity)
|
||||||
.leftJoin(auditLogEntity)
|
.leftJoin(auditLogEntity)
|
||||||
.on(errorLogEntity.id.eq(auditLogEntity.errorLogUid))
|
.on(errorLogEntity.id.eq(auditLogEntity.errorLogUid))
|
||||||
@@ -94,12 +100,14 @@ public class ErrorLogRepositoryImpl implements ErrorLogRepositoryCustom {
|
|||||||
if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
|
if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
LocalDateTime startDateTime = startDate.atStartOfDay();
|
|
||||||
LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();
|
ZoneId zoneId = ZoneId.of("Asia/Seoul");
|
||||||
|
ZonedDateTime startDateTime = startDate.atStartOfDay(zoneId);
|
||||||
|
ZonedDateTime endDateTime = endDate.plusDays(1).atStartOfDay(zoneId);
|
||||||
return auditLogEntity
|
return auditLogEntity
|
||||||
.createdDate
|
.createdDate
|
||||||
.goe(ZonedDateTime.from(startDateTime))
|
.goe(startDateTime)
|
||||||
.and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime)));
|
.and(auditLogEntity.createdDate.lt(endDateTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BooleanExpression eventStatusEqFailed() {
|
private BooleanExpression eventStatusEqFailed() {
|
||||||
|
|||||||
Reference in New Issue
Block a user