Skip to content

Commit c386ff0

Browse files
authored
Merge pull request #6 from prgrms-web-devcourse-final-project/feat#2/start_kit_setting
[feat] 스타터 킷 설정#1
2 parents f13b880 + ce6ca89 commit c386ff0

File tree

15 files changed

+456
-2
lines changed

15 files changed

+456
-2
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
2929
implementation("org.springframework.boot:spring-boot-starter-validation")
3030
implementation("org.springframework.boot:spring-boot-starter-web")
31+
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
3132
compileOnly("org.projectlombok:lombok")
3233
developmentOnly("org.springframework.boot:spring-boot-devtools")
3334
runtimeOnly("com.h2database:h2")

src/main/java/com/back/BackApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.scheduling.annotation.EnableScheduling;
56

67
@SpringBootApplication
8+
@EnableScheduling
79
public class BackApplication {
810

911
public static void main(String[] args) {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.back.global.appConfig;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
5+
@Configuration
6+
public class AppConfig {
7+
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.back.global.appConfig;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
5+
6+
@Configuration
7+
@EnableJpaAuditing
8+
public class JpaAuditingConfig {
9+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.back.global.appConfig;
2+
3+
import io.swagger.v3.oas.models.OpenAPI;
4+
import io.swagger.v3.oas.models.info.Info;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
8+
@Configuration
9+
public class SwaggerConfig {
10+
11+
@Bean
12+
public OpenAPI openAPI() {
13+
return new OpenAPI()
14+
.info(new Info()
15+
.title("HaeDokCoding API")
16+
.description("HaeDokCoding Backend API Documentation")
17+
.version("v1.0.0"));
18+
}
19+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.back.global.aspect;
2+
3+
import com.back.global.rsData.RsData;
4+
import jakarta.servlet.http.HttpServletResponse;
5+
import org.aspectj.lang.ProceedingJoinPoint;
6+
import org.aspectj.lang.annotation.Around;
7+
import org.aspectj.lang.annotation.Aspect;
8+
import org.springframework.stereotype.Component;
9+
import org.springframework.web.context.request.RequestContextHolder;
10+
import org.springframework.web.context.request.ServletRequestAttributes;
11+
12+
@Aspect
13+
@Component
14+
public class ResponseAspect {
15+
16+
@Around("""
17+
execution(public com.back.global.rsData.RsData *(..)) &&
18+
(
19+
within(@org.springframework.stereotype.Controller *) ||
20+
within(@org.springframework.web.bind.annotation.RestController *)
21+
) &&
22+
(
23+
@annotation(org.springframework.web.bind.annotation.GetMapping) ||
24+
@annotation(org.springframework.web.bind.annotation.PostMapping) ||
25+
@annotation(org.springframework.web.bind.annotation.PutMapping) ||
26+
@annotation(org.springframework.web.bind.annotation.DeleteMapping) ||
27+
@annotation(org.springframework.web.bind.annotation.RequestMapping)
28+
)
29+
""")
30+
public Object handleResponse(ProceedingJoinPoint joinPoint) throws Throwable {
31+
Object proceed = joinPoint.proceed();
32+
33+
RsData<?> rsData = (RsData<?>) proceed;
34+
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
35+
if (response != null) {
36+
response.setStatus(rsData.code());
37+
}
38+
39+
return proceed;
40+
}
41+
}
42+
43+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.back.global.controller;
2+
3+
import org.springframework.stereotype.Controller;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
6+
@Controller
7+
public class HomeController {
8+
9+
@GetMapping("/")
10+
public String redirectToSwagger() {
11+
return "redirect:/swagger-ui/index.html";
12+
}
13+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.back.global.exception;
2+
3+
4+
import com.back.global.rsData.RsData;
5+
6+
/**
7+
* 서비스 예외를 나타내는 클래스
8+
* 서비스 계층에서 발생하는 오류를 처리하기 위해 사용
9+
* @param code 오류 코드
10+
* @param msg 오류 메시지
11+
*/
12+
13+
public class ServiceException extends RuntimeException {
14+
private final int code;
15+
private final String msg;
16+
17+
public ServiceException(int code, String msg) {
18+
super(code + " : " + msg);
19+
this.code = code;
20+
this.msg = msg;
21+
}
22+
public RsData<Void> getRsData() {
23+
return RsData.of(code,msg);
24+
}
25+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package com.back.global.globalExceptionHandler;
2+
3+
4+
import com.back.global.exception.ServiceException;
5+
import com.back.global.rsData.RsData;
6+
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import jakarta.validation.ConstraintViolationException;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.http.converter.HttpMessageNotReadableException;
11+
import org.springframework.validation.FieldError;
12+
import org.springframework.web.bind.MethodArgumentNotValidException;
13+
import org.springframework.web.bind.MissingRequestHeaderException;
14+
import org.springframework.web.bind.annotation.ExceptionHandler;
15+
import org.springframework.web.bind.annotation.RestControllerAdvice;
16+
17+
import java.io.IOException;
18+
import java.util.Comparator;
19+
import java.util.NoSuchElementException;
20+
import java.util.stream.Collectors;
21+
22+
import static org.springframework.http.HttpStatus.BAD_REQUEST;
23+
import static org.springframework.http.HttpStatus.NOT_FOUND;
24+
25+
/**
26+
* 글로벌 예외 핸들러 클래스
27+
* 각 예외에 대한 적절한 HTTP 상태 코드와 메시지를 포함한 응답 반환
28+
* 400: Bad Request
29+
* 404: Not Found
30+
* 500: Internal Server Error
31+
*/
32+
33+
@RestControllerAdvice
34+
public class GlobalExceptionHandler {
35+
36+
// ServiceException: 서비스 계층에서 발생하는 커스텀 예외
37+
@ExceptionHandler(ServiceException.class)
38+
public ResponseEntity<RsData<Void>> handle(ServiceException ex) {
39+
RsData<Void> rsData = ex.getRsData();
40+
int statusCode = rsData.code();
41+
42+
HttpStatus status = HttpStatus.resolve(statusCode);
43+
if( status == null) {
44+
status = HttpStatus.INTERNAL_SERVER_ERROR; // 기본값 설정
45+
}
46+
return ResponseEntity.status(status).body(rsData);
47+
48+
}
49+
50+
// NoSuchElementException: 데이터 없을떄 예외
51+
@ExceptionHandler(NoSuchElementException.class)
52+
public ResponseEntity<RsData<Void>> handle(NoSuchElementException ex) {
53+
return new ResponseEntity<>(
54+
RsData.of(
55+
404,
56+
"해당 데이터가 존재하지 않습니다"
57+
),
58+
NOT_FOUND
59+
);
60+
}
61+
62+
//ConstraintViolationException: 제약 조건(@NotNull, @Size 등)을 어겼을 때 발생예외
63+
@ExceptionHandler(ConstraintViolationException.class)
64+
public ResponseEntity<RsData<Void>> handle(ConstraintViolationException ex) {
65+
//메세지 형식 : <필드명>-<검증어노테이션명>-<검증실패메시지>
66+
String message = ex.getConstraintViolations()
67+
.stream()
68+
.map(
69+
violation -> {
70+
String path = violation.getPropertyPath().toString();
71+
String field = path.contains(".") ? path.split("\\.",2)[1]: path;
72+
String[] messageTemplateBits = violation.getMessageTemplate()
73+
.split("\\.");
74+
String code = messageTemplateBits.length >= 2
75+
? messageTemplateBits[messageTemplateBits.length -2] : "Unknown";
76+
77+
String _message = violation.getMessage();
78+
79+
return "%s-%s-%s".formatted(field, code, _message);
80+
})
81+
.sorted()
82+
.collect(Collectors.joining("\n"));
83+
84+
return new ResponseEntity<>(
85+
RsData.of(
86+
400,
87+
message
88+
),
89+
BAD_REQUEST
90+
);
91+
}
92+
93+
94+
// MethodArgumentNotValidException: @Valid 어노테이션을 사용한 유효성 검사 실패시 발생하는 예외
95+
@ExceptionHandler(MethodArgumentNotValidException.class)
96+
public ResponseEntity<RsData<Void>> handle(MethodArgumentNotValidException ex) {
97+
//메세지 형식 : <필드명>-<검증어노테이션명>-<검증실패메시지>
98+
String message = ex.getBindingResult()
99+
.getAllErrors()
100+
.stream()
101+
.filter(error -> error instanceof FieldError)
102+
.map(error -> (FieldError) error)
103+
.map(error -> error.getField() + "-" + error.getCode() + "-" + error.getDefaultMessage())
104+
.sorted(Comparator.comparing(String::toString))
105+
.collect(Collectors.joining("\n"));
106+
107+
return new ResponseEntity<>(
108+
RsData.of(
109+
400,
110+
message
111+
) ,
112+
BAD_REQUEST
113+
);
114+
}
115+
116+
// HttpMessageNotReadableException : 요청 본문이 올바르지 않을 때 발생하는 예외
117+
@ExceptionHandler(HttpMessageNotReadableException.class)
118+
public ResponseEntity<RsData<Void>> handle(HttpMessageNotReadableException ex) {
119+
return new ResponseEntity<>(
120+
RsData.of(
121+
400,
122+
"요청 본문이 올바르지 않습니다."
123+
),
124+
BAD_REQUEST
125+
);
126+
}
127+
128+
// MissingRequestHeaderException : 필수 요청 헤더가 누락되었을 때 발생하는 예외
129+
@ExceptionHandler(MissingRequestHeaderException.class)
130+
public ResponseEntity<RsData<Void>> handle(MissingRequestHeaderException ex) {
131+
// 메세지 형식 : <필드명>-<검증어노테이션명>-<검증실패메시지>
132+
String message = "%s-%s-%s".formatted(
133+
ex.getHeaderName(),
134+
"NotBlank",
135+
ex.getLocalizedMessage()
136+
);
137+
138+
return new ResponseEntity<>(
139+
RsData.of(
140+
400,
141+
message
142+
),
143+
BAD_REQUEST
144+
);
145+
}
146+
147+
@ExceptionHandler(JsonProcessingException.class)
148+
public ResponseEntity<RsData<Void>> handleJsonProcessingException(JsonProcessingException e) {
149+
return ResponseEntity.badRequest()
150+
.body(RsData.of(400, "JSON 파싱 오류가 발생했습니다.", null));
151+
}
152+
153+
@ExceptionHandler(IOException.class)
154+
public ResponseEntity<RsData<Void>> handleIOException(IOException e) {
155+
return ResponseEntity.internalServerError()
156+
.body(RsData.of(500, "서버 내부 오류가 발생했습니다.", null));
157+
}
158+
159+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.back.global.init;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.beans.factory.annotation.Autowired;
5+
import org.springframework.boot.ApplicationRunner;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.context.annotation.Lazy;
9+
import org.springframework.context.annotation.Profile;
10+
11+
@Configuration
12+
@Profile("dev")
13+
@RequiredArgsConstructor
14+
public class DevInitData {
15+
@Autowired
16+
@Lazy
17+
private DevInitData self;
18+
19+
20+
@Bean
21+
ApplicationRunner devInitDataApplicationRunner() {
22+
return args -> {
23+
// self.memberInit();
24+
25+
};
26+
}
27+
28+
// @Transactional
29+
// public void memberInit() {
30+
// if (memberService.count() > 0) {
31+
// return;
32+
// }
33+
// }
34+
35+
}

0 commit comments

Comments
 (0)