Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ dependencies {
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

//validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
}

tasks.named('test') {
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/rootbox/rootboxApp/api/user/business/UserMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package rootbox.rootboxApp.api.user.business;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import rootbox.rootboxApp.global.entity.User;

import java.util.Optional;

@Component
@RequiredArgsConstructor
public class UserMapper {

private final UserService userService;
private static UserService staticUserService;

@PostConstruct
public void init(){
staticUserService = this.userService;
}

public static Optional<User> toUserSecurity(String id){
return staticUserService.findById(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package rootbox.rootboxApp.api.user.business;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import rootbox.rootboxApp.api.user.implementation.UserQueryAdapter;
import rootbox.rootboxApp.global.entity.User;

import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {

private final UserQueryAdapter userQueryAdapter;


Optional<User> findById(String id) {
return userQueryAdapter.findUserByIdSecurity(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package rootbox.rootboxApp.api.user.implementation;

import lombok.RequiredArgsConstructor;
import rootbox.rootboxApp.api.user.persistence.UserRepository;
import rootbox.rootboxApp.global.annotations.Adapter;
import rootbox.rootboxApp.global.entity.User;

import java.util.Optional;

@Adapter
@RequiredArgsConstructor
public class UserQueryAdapter {

private final UserRepository userRepository;

public Optional<User> findUserByIdSecurity(String userId){
return userRepository.findById(Long.valueOf(userId));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package rootbox.rootboxApp.api.user.persistence;

import org.springframework.data.jpa.repository.JpaRepository;
import rootbox.rootboxApp.global.entity.User;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

public Optional<User> findById(Long id);
}
16 changes: 16 additions & 0 deletions src/main/java/rootbox/rootboxApp/global/annotations/Adapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package rootbox.rootboxApp.global.annotations;

import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Adapter {
// 스프링 빈의 이름을 지정
@AliasFor(annotation = Component.class)
String value() default "";
}
58 changes: 58 additions & 0 deletions src/main/java/rootbox/rootboxApp/global/common/CommonResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package rootbox.rootboxApp.global.common;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@AllArgsConstructor
@JsonPropertyOrder( {"isSuccess", "code", "message", "data"} )
public class CommonResponse<T> {
@Override
public String toString() {
try {
ObjectMapper mapper = new ObjectMapper();
// Java 8 날짜/시간 모듈 등록
mapper.registerModule(new JavaTimeModule());
// 날짜와 시간을 ISO-8601 형식의 문자열로 직렬화
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 이쁘게 출력하기
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

@JsonProperty("isSuccess")
private Boolean isSuccess;
@JsonProperty("code")
private String code;
@JsonProperty("message")
private String message;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd")
@JsonProperty("createdAt")
private LocalDateTime createdAt;
@JsonProperty("data")
private T data;

// 성공한 경우 응답 생성

public static <T> CommonResponse<T> onSuccess(T data){
return new CommonResponse<>(true, "200" , "요청에 성공하였습니다.", LocalDateTime.now(), data);
}

// 실패한 경우 응답 생성
public static <T> CommonResponse<T> onFailure(String code, String message, T data){
return new CommonResponse<>(false,code, message, LocalDateTime.now(), data);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package rootbox.rootboxApp.global.common.exception;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import rootbox.rootboxApp.global.common.CommonResponse;
import rootbox.rootboxApp.global.common.exception.base.GeneralException;
import rootbox.rootboxApp.global.common.exception.base.GlobalErrorCode;
import rootbox.rootboxApp.global.common.exception.dto.ErrorResponseDTO;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

@Slf4j
@RestControllerAdvice(annotations = {RestController.class})
public class ExceptionAdvice extends ResponseEntityExceptionHandler {


@org.springframework.web.bind.annotation.ExceptionHandler
public ResponseEntity<Object> validation(ConstraintViolationException e, WebRequest request) {
String errorMessage = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.findFirst()
.orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생"));

return handleExceptionInternalConstraint(e, GlobalErrorCode.valueOf(errorMessage), request);
}

@NotNull
public ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) {

Map<String, String> errors = new LinkedHashMap<>();

e.getBindingResult().getFieldErrors()
.forEach(fieldError -> {
String fieldName = fieldError.getField();
String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse("");
errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage);
});

return handleExceptionInternalArgs(e, GlobalErrorCode.valueOf("BAD_REQUEST"),request,errors);
}

@org.springframework.web.bind.annotation.ExceptionHandler
public ResponseEntity<Object> exception(Exception e, WebRequest request) {
e.printStackTrace();

return handleExceptionInternalFalse(e, GlobalErrorCode.SERVER_ERROR.getHttpStatus(),request, e.getMessage());
}

@ExceptionHandler(value = GeneralException.class)
public ResponseEntity onThrowException(GeneralException generalException,
@AuthenticationPrincipal User user, HttpServletRequest request) {
getExceptionStackTrace(generalException, user, request);
ErrorResponseDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus();
System.out.println(generalException.getMessage());
System.out.println(generalException.getErrorCode());
return handleExceptionInternal(generalException,errorReasonHttpStatus, request);
}


private ResponseEntity<Object> handleExceptionInternal(Exception e, ErrorResponseDTO reason,
HttpServletRequest request) {

CommonResponse<Object> body = CommonResponse.onFailure(reason.getCode(),reason.getMessage(),null);
// e.printStackTrace();

WebRequest webRequest = new ServletWebRequest(request);
return super.handleExceptionInternal(
e,
body,
null,
reason.getHttpStatus(),
webRequest
);
}

private ResponseEntity<Object> handleExceptionInternalFalse(Exception e,
HttpStatus status, WebRequest request, String errorPoint) {
CommonResponse<Object> body = CommonResponse.onFailure(GlobalErrorCode.SERVER_ERROR.getCode(), GlobalErrorCode.SERVER_ERROR.getMessage(),errorPoint);
return super.handleExceptionInternal(
e,
body,
HttpHeaders.EMPTY,
status,
request
);
}

private ResponseEntity<Object> handleExceptionInternalArgs(Exception e, GlobalErrorCode errorCommonStatus,
WebRequest request, Map<String, String> errorArgs) {
CommonResponse<Object> body = CommonResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorArgs);
return super.handleExceptionInternal(
e,
body,
HttpHeaders.EMPTY,
errorCommonStatus.getHttpStatus(),
request
);
}

private ResponseEntity<Object> handleExceptionInternalConstraint(Exception e, GlobalErrorCode errorCommonStatus,
WebRequest request) {
CommonResponse<Object> body = CommonResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null);
return super.handleExceptionInternal(
e,
body,
HttpHeaders.EMPTY,
errorCommonStatus.getHttpStatus(),
request
);
}

private void getExceptionStackTrace(Exception e, @AuthenticationPrincipal User user,
HttpServletRequest request) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);

pw.append("\n==========================!!!ERROR TRACE!!!==========================\n");
pw.append("uri: ").append(request.getRequestURI()).append(" ").append(request.getMethod()).append("\n");
if (user != null) {
pw.append("uid: ").append(user.getUsername()).append("\n");
}
pw.append(e.getMessage());
System.out.println(e.getMessage());
pw.append("\n=====================================================================");
log.error(sw.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package rootbox.rootboxApp.global.common.exception.base;

import rootbox.rootboxApp.global.common.exception.dto.ErrorResponseDTO;

public interface BaseErrorCode {

public ErrorResponseDTO getReason();

public ErrorResponseDTO getReasonHttpStatus();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package rootbox.rootboxApp.global.common.exception.base;

import lombok.AllArgsConstructor;
import lombok.Getter;
import rootbox.rootboxApp.global.common.exception.dto.ErrorResponseDTO;

@Getter
@AllArgsConstructor
public class GeneralException extends RuntimeException {

private final BaseErrorCode errorCode;

public ErrorResponseDTO getErrorReason() {
return this.errorCode.getReason();
}

public ErrorResponseDTO getErrorReasonHttpStatus() {
return this.errorCode.getReasonHttpStatus();
}

public String getErrorCode(){
return this.errorCode.getReason().getCode();
}
}
Loading