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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2")
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/back/BackApplication.java
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.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class BackApplication {

public static void main(String[] args) {
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/back/global/appConfig/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.back.global.appConfig;

import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.back.global.appConfig;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}
19 changes: 19 additions & 0 deletions src/main/java/com/back/global/appConfig/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.back.global.appConfig;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("HaeDokCoding API")
.description("HaeDokCoding Backend API Documentation")
.version("v1.0.0"));
}
}
43 changes: 43 additions & 0 deletions src/main/java/com/back/global/aspect/ResponseAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.back.global.aspect;

import com.back.global.rsData.RsData;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
public class ResponseAspect {

@Around("""
execution(public com.back.global.rsData.RsData *(..)) &&
(
within(@org.springframework.stereotype.Controller *) ||
within(@org.springframework.web.bind.annotation.RestController *)
) &&
(
@annotation(org.springframework.web.bind.annotation.GetMapping) ||
@annotation(org.springframework.web.bind.annotation.PostMapping) ||
@annotation(org.springframework.web.bind.annotation.PutMapping) ||
@annotation(org.springframework.web.bind.annotation.DeleteMapping) ||
@annotation(org.springframework.web.bind.annotation.RequestMapping)
)
""")
public Object handleResponse(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed = joinPoint.proceed();

RsData<?> rsData = (RsData<?>) proceed;
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
if (response != null) {
response.setStatus(rsData.code());
}

return proceed;
}
}


13 changes: 13 additions & 0 deletions src/main/java/com/back/global/controller/HomeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.back.global.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

@GetMapping("/")
public String redirectToSwagger() {
return "redirect:/swagger-ui/index.html";
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/back/global/exception/ServiceException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.back.global.exception;


import com.back.global.rsData.RsData;

/**
* 서비스 예외를 나타내는 클래스
* 서비스 계층에서 발생하는 오류를 처리하기 위해 사용
* @param code 오류 코드
* @param msg 오류 메시지
*/

public class ServiceException extends RuntimeException {
private final int code;
private final String msg;

public ServiceException(int code, String msg) {
super(code + " : " + msg);
this.code = code;
this.msg = msg;
}
public RsData<Void> getRsData() {
return RsData.of(code,msg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package com.back.global.globalExceptionHandler;


import com.back.global.exception.ServiceException;
import com.back.global.rsData.RsData;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.io.IOException;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;

/**
* 글로벌 예외 핸들러 클래스
* 각 예외에 대한 적절한 HTTP 상태 코드와 메시지를 포함한 응답 반환
* 400: Bad Request
* 404: Not Found
* 500: Internal Server Error
*/

@RestControllerAdvice
public class GlobalExceptionHandler {

// ServiceException: 서비스 계층에서 발생하는 커스텀 예외
@ExceptionHandler(ServiceException.class)
public ResponseEntity<RsData<Void>> handle(ServiceException ex) {
RsData<Void> rsData = ex.getRsData();
int statusCode = rsData.code();

HttpStatus status = HttpStatus.resolve(statusCode);
if( status == null) {
status = HttpStatus.INTERNAL_SERVER_ERROR; // 기본값 설정
}
return ResponseEntity.status(status).body(rsData);

}

// NoSuchElementException: 데이터 없을떄 예외
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<RsData<Void>> handle(NoSuchElementException ex) {
return new ResponseEntity<>(
RsData.of(
404,
"해당 데이터가 존재하지 않습니다"
),
NOT_FOUND
);
}

//ConstraintViolationException: 제약 조건(@NotNull, @Size 등)을 어겼을 때 발생예외
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<RsData<Void>> handle(ConstraintViolationException ex) {
//메세지 형식 : <필드명>-<검증어노테이션명>-<검증실패메시지>
String message = ex.getConstraintViolations()
.stream()
.map(
violation -> {
String path = violation.getPropertyPath().toString();
String field = path.contains(".") ? path.split("\\.",2)[1]: path;
String[] messageTemplateBits = violation.getMessageTemplate()
.split("\\.");
String code = messageTemplateBits.length >= 2
? messageTemplateBits[messageTemplateBits.length -2] : "Unknown";

String _message = violation.getMessage();

return "%s-%s-%s".formatted(field, code, _message);
})
.sorted()
.collect(Collectors.joining("\n"));

return new ResponseEntity<>(
RsData.of(
400,
message
),
BAD_REQUEST
);
}


// MethodArgumentNotValidException: @Valid 어노테이션을 사용한 유효성 검사 실패시 발생하는 예외
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<RsData<Void>> handle(MethodArgumentNotValidException ex) {
//메세지 형식 : <필드명>-<검증어노테이션명>-<검증실패메시지>
String message = ex.getBindingResult()
.getAllErrors()
.stream()
.filter(error -> error instanceof FieldError)
.map(error -> (FieldError) error)
.map(error -> error.getField() + "-" + error.getCode() + "-" + error.getDefaultMessage())
.sorted(Comparator.comparing(String::toString))
.collect(Collectors.joining("\n"));

return new ResponseEntity<>(
RsData.of(
400,
message
) ,
BAD_REQUEST
);
}

// HttpMessageNotReadableException : 요청 본문이 올바르지 않을 때 발생하는 예외
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<RsData<Void>> handle(HttpMessageNotReadableException ex) {
return new ResponseEntity<>(
RsData.of(
400,
"요청 본문이 올바르지 않습니다."
),
BAD_REQUEST
);
}

// MissingRequestHeaderException : 필수 요청 헤더가 누락되었을 때 발생하는 예외
@ExceptionHandler(MissingRequestHeaderException.class)
public ResponseEntity<RsData<Void>> handle(MissingRequestHeaderException ex) {
// 메세지 형식 : <필드명>-<검증어노테이션명>-<검증실패메시지>
String message = "%s-%s-%s".formatted(
ex.getHeaderName(),
"NotBlank",
ex.getLocalizedMessage()
);

return new ResponseEntity<>(
RsData.of(
400,
message
),
BAD_REQUEST
);
}

@ExceptionHandler(JsonProcessingException.class)
public ResponseEntity<RsData<Void>> handleJsonProcessingException(JsonProcessingException e) {
return ResponseEntity.badRequest()
.body(RsData.of(400, "JSON 파싱 오류가 발생했습니다.", null));
}

@ExceptionHandler(IOException.class)
public ResponseEntity<RsData<Void>> handleIOException(IOException e) {
return ResponseEntity.internalServerError()
.body(RsData.of(500, "서버 내부 오류가 발생했습니다.", null));
}

}
35 changes: 35 additions & 0 deletions src/main/java/com/back/global/init/DevInitData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.back.global.init;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("dev")
@RequiredArgsConstructor
public class DevInitData {
@Autowired
@Lazy
private DevInitData self;


@Bean
ApplicationRunner devInitDataApplicationRunner() {
return args -> {
// self.memberInit();

};
}

// @Transactional
// public void memberInit() {
// if (memberService.count() > 0) {
// return;
// }
// }

}
42 changes: 42 additions & 0 deletions src/main/java/com/back/global/init/TestInitData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.back.global.init;


import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("test")
@RequiredArgsConstructor
public class TestInitData {
@Autowired
@Lazy
private TestInitData self;

@Bean
ApplicationRunner testInitDataApplicationRunner() {
return args -> {
// self.memberInit();

};
}

// @Transactional
// public void memberInit() {
// if (memberService.count() > 0) {
// return;
// }
//
// memberService.join("system","12345678", "[email protected]");
// memberService.join("admin","12345678", "[email protected]");
// memberService.join("user1","12345678", "[email protected]");
// memberService.join("user2","12345678", "[email protected]");
// memberService.join("user3","12345678", "[email protected]");
// memberService.join("user4","12345678", "[email protected]");
// }

}
Loading