Skip to content

[Merge] main to deploy #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 12, 2025
Merged
118 changes: 51 additions & 67 deletions ontime-back/src/main/java/devkor/ontime_back/LoggingAspect.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package devkor.ontime_back;

import devkor.ontime_back.dto.RequestInfoDto;
import devkor.ontime_back.entity.ApiLog;
import devkor.ontime_back.repository.ApiLogRepository;
import devkor.ontime_back.response.GeneralException;
import devkor.ontime_back.service.ApiLogService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
Expand All @@ -13,48 +16,36 @@
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.annotation.Annotation;
import java.util.Map;


@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LoggingAspect {

private final ApiLogRepository apiLogRepository;

public LoggingAspect(ApiLogRepository apiLogRepository) {
this.apiLogRepository = apiLogRepository;
}

private final ApiLogService apiLogService;
private static final String NO_PARAMS = "No Params";
private static final String NO_BODY = "No Body";

@Pointcut("bean(*Controller)")
private void allRequest() {}

@Around("allRequest()")
public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

// requestUrl
String requestUrl = request.getRequestURI();
// requestMethod
String requestMethod = request.getMethod();
// userId
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = (authentication != null && authentication.isAuthenticated())
? authentication.getName() // 인증된 사용자의 이름 (주로 ID로 사용됨)
: "Anonymous";
// clientIp
String clientIp = request.getRemoteAddr();
RequestInfoDto requestInfoDto = extractRequestInfo();

// requestTime
long beforeRequest = System.currentTimeMillis();
Expand Down Expand Up @@ -91,21 +82,14 @@ public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {

// 정상 요청 로그 저장
long timeTaken = System.currentTimeMillis() - beforeRequest;
ApiLog apiLog = ApiLog.builder().
requestUrl(requestUrl).
requestMethod(requestMethod).
userId(userId).
clientIp(clientIp).
responseStatus(responseStatus).
takenTime(timeTaken).
build();

apiLogRepository.save(apiLog);
ApiLog apiLog = buildApiLog(requestInfoDto, responseStatus, timeTaken);
apiLogService.saveLog(apiLog);

log.info("[Request Log] requestUrl: {}, requestMethod: {}, userId: {}, clientIp: {}, pathVariable: {}, requestBody: {}, responseStatus: {}, timeTaken: {}",
requestUrl, requestMethod, userId, clientIp,
pathVariable != null ? pathVariable : "No Params",
requestBody != null ? requestBody : "No Body",
requestInfoDto.getRequestUrl(), requestInfoDto.getRequestMethod(), requestInfoDto.getUserId(), requestInfoDto.getClientIp(),
pathVariable != null ? pathVariable : NO_PARAMS,
requestBody != null ? requestBody : NO_BODY,
responseStatus, timeTaken);

return result;
Expand All @@ -117,19 +101,7 @@ public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {

@AfterThrowing(pointcut = "allRequest()", throwing = "ex")
public void logException(JoinPoint joinPoint, Exception ex) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

// requestUrl
String requestUrl = request.getRequestURI();
// requestMethod
String requestMethod = request.getMethod();
// userId
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = (authentication != null && authentication.isAuthenticated())
? authentication.getName() // 인증된 사용자의 이름 (주로 ID로 사용됨)
: "Anonymous";
// clientIp
String clientIp = request.getRemoteAddr();
RequestInfoDto requestInfoDto = extractRequestInfo();

// exceptionName
String exceptionName;
Expand All @@ -144,32 +116,44 @@ public void logException(JoinPoint joinPoint, Exception ex) {
int responseStatus = mapExceptionToStatusCode(ex);

log.error("[Error Log] requestUrl: {}, requestMethod: {}, userId: {}, clientIp: {}, exception: {}, message: {}, responseStatus: {}",
requestUrl, requestMethod, userId, clientIp, exceptionName, exceptionMessage, responseStatus);

// DB에 에러 로그 저장
ApiLog errorLog = ApiLog.builder().
requestUrl(requestUrl).
requestMethod(requestMethod).
userId(userId).
clientIp(clientIp).
responseStatus(responseStatus).
takenTime(0).
build();
// 상태 코드와 시간은 예제로 설정
apiLogRepository.save(errorLog);
requestInfoDto.getRequestUrl(), requestInfoDto.getRequestMethod(), requestInfoDto.getUserId(), requestInfoDto.getClientIp(), exceptionName, exceptionMessage, responseStatus);

ApiLog errorLog = buildApiLog(requestInfoDto, responseStatus, 0);
apiLogService.saveLog(errorLog);
}

// requestinfo 추출
private RequestInfoDto extractRequestInfo() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

String requestUrl = request.getRequestURI();
String requestMethod = request.getMethod();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = (authentication != null && authentication.isAuthenticated())
? authentication.getName()
: "Anonymous";
String clientIp = request.getRemoteAddr();

return new RequestInfoDto(requestUrl, requestMethod, userId, clientIp);
}

// apilog 생성
private ApiLog buildApiLog(RequestInfoDto info, int responseStatus, long timeTaken) {
return ApiLog.builder()
.requestUrl(info.getRequestUrl())
.requestMethod(info.getRequestMethod())
.userId(info.getUserId())
.clientIp(info.getClientIp())
.responseStatus(responseStatus)
.takenTime(timeTaken)
.build();
}

private int mapExceptionToStatusCode(Exception e) {
if (e instanceof IllegalArgumentException) {
return 400; // Bad Request
} else if (e instanceof org.springframework.security.access.AccessDeniedException) {
return 403; // Forbidden
} else if (e instanceof org.springframework.web.bind.MethodArgumentNotValidException) {
return 422; // Unprocessable Entity
} else {
return 500; // Internal Server Error
if (e instanceof GeneralException ge) {
return ge.getErrorCode().getCode();
}
return 500;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableAsync
@SpringBootApplication
@EnableScheduling
public class OntimeBackApplication {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@Configuration
@OpenAPIDefinition(
servers = {
@Server(url = "https://ontime.devkor.club", description = "Production Server"),
@Server(url = "https://api.ontime.devkor.club", description = "Production Server"),
@Server(url = "http://localhost:8080", description = "Local Serever")
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package devkor.ontime_back.dto;


import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class RequestInfoDto {
private String requestUrl;
private String requestMethod;
private String userId;
private String clientIp;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package devkor.ontime_back.dto;

import devkor.ontime_back.entity.Place;
import devkor.ontime_back.entity.Schedule;
import devkor.ontime_back.entity.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -14,22 +17,29 @@
@AllArgsConstructor
public class ScheduleAddDto {
private UUID scheduleId;

private UUID placeId;

private String placeName;

private String scheduleName;

private Integer moveTime; // 이동시간

private LocalDateTime scheduleTime; // 약속시각

private Boolean isChange; // 변경여부

private Boolean isStarted; // 버튼누름여부

private Integer scheduleSpareTime; // 스케줄 별 여유시간

private String scheduleNote; // 스케줄 별 주의사항

public Schedule toEntity(User user, Place place) {
return Schedule.builder()
.user(user)
.scheduleId(this.scheduleId)
.place(place)
.scheduleName(this.scheduleName)
.moveTime(this.moveTime)
.scheduleTime(this.scheduleTime)
.isChange(false)
.isStarted(false)
.scheduleSpareTime(this.scheduleSpareTime)
.latenessTime(-1)
.scheduleNote(this.scheduleNote)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package devkor.ontime_back.entity;

import devkor.ontime_back.dto.ScheduleModDto;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.OnDelete;
Expand Down Expand Up @@ -54,14 +55,14 @@ public class Schedule {
@Column(columnDefinition = "TEXT") // 명시적으로 TEXT 타입으로 정의
private String scheduleNote; // 스케줄 별 주의사항

public void updateSchedule(Place place, String scheduleName, Integer moveTime, LocalDateTime scheduleTime, Integer scheduleSpareTime, Integer latenessTime, String scheduleNote) {
public void updateSchedule(Place place, ScheduleModDto scheduleModDto) {
this.place = place;
this.scheduleName = scheduleName;
this.moveTime = moveTime;
this.scheduleTime = scheduleTime;
this.scheduleSpareTime = scheduleSpareTime;
this.latenessTime = latenessTime;
this.scheduleNote = scheduleNote;
this.scheduleName = scheduleModDto.getScheduleName();
this.moveTime = scheduleModDto.getMoveTime();
this.scheduleTime = scheduleModDto.getScheduleTime();
this.scheduleSpareTime = scheduleModDto.getScheduleSpareTime();
this.latenessTime = scheduleModDto.getLatenessTime();
this.scheduleNote = scheduleModDto.getScheduleNote();
}

public void startSchedule() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
public class ApiResponseForm<T> {
// 제네릭 api 응답 객체
private String status;
private String code;
private int code;
private String message;
private final T data;
public ApiResponseForm(String status, String code, String message, T data) {
public ApiResponseForm(String status, int code, String message, T data) {
this.status = status; // HttpResponse의 생성자 호출 (부모 클래스의 생성자 또는 메서드를 호출, 자식 클래스는 부모 클래스의 private 필드에 직접 접근 X)
this.code = code;
this.message = message;
Expand All @@ -20,33 +20,33 @@ public ApiResponseForm(String status, String code, String message, T data) {

// 성공 응답을 위한 메서드 (message를 받는 경우)
public static <T> ApiResponseForm<T> success(T data, String message) {
return new ApiResponseForm<>("success", "200", message, data);
return new ApiResponseForm<>("success", 200, message, data);
}

// 성공 응답을 위한 메서드 (message를 받지 않는 경우)
public static <T> ApiResponseForm<T> success(T data) {
return new ApiResponseForm<>("success", "200", "OK", data);
return new ApiResponseForm<>("success", 200, "OK", data);
}

// 실패 응답을 위한 메서드
public static <T> ApiResponseForm<T> fail(String code, String message) {
public static <T> ApiResponseForm<T> fail(int code, String message) {
return new ApiResponseForm<>("fail", code, message, null); // 실패의 경우 data는 null로 처리
}

public static <T> ApiResponseForm<T> accessTokenEmpty(String code, String message) {
public static <T> ApiResponseForm<T> accessTokenEmpty(int code, String message) {
return new ApiResponseForm<>("accessTokenEmpty", code, message, null); // 실패의 경우 data는 null로 처리
}

public static <T> ApiResponseForm<T> accessTokenInvalid(String code, String message) {
public static <T> ApiResponseForm<T> accessTokenInvalid(int code, String message) {
return new ApiResponseForm<>("accessTokenInvalid", code, message, null); // 실패의 경우 data는 null로 처리
}

public static <T> ApiResponseForm<T> refreshTokenInvalid(String code, String message) {
public static <T> ApiResponseForm<T> refreshTokenInvalid(int code, String message) {
return new ApiResponseForm<>("refreshTokenInvalid", code, message, null); // 실패의 경우 data는 null로 처리
}

// 오류 응답을 위한 메서드
public static <T> ApiResponseForm<T> error(String code, String message) {
public static <T> ApiResponseForm<T> error(int code, String message) {
return new ApiResponseForm<>("error", code, message, null); // 오류의 경우 data는 null로 처리
}

Expand Down
Loading