Skip to content

Commit 6d5a08d

Browse files
committed
feat : 글로벌 클래스 start kit 설정1
1 parent 591579f commit 6d5a08d

File tree

11 files changed

+403
-2
lines changed

11 files changed

+403
-2
lines changed
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: 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: 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+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.back.global.init;
2+
3+
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.boot.ApplicationRunner;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.context.annotation.Lazy;
10+
import org.springframework.context.annotation.Profile;
11+
12+
@Configuration
13+
@Profile("test")
14+
@RequiredArgsConstructor
15+
public class TestInitData {
16+
@Autowired
17+
@Lazy
18+
private TestInitData self;
19+
20+
@Bean
21+
ApplicationRunner testInitDataApplicationRunner() {
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+
// memberService.join("system","12345678", "[email protected]");
35+
// memberService.join("admin","12345678", "[email protected]");
36+
// memberService.join("user1","12345678", "[email protected]");
37+
// memberService.join("user2","12345678", "[email protected]");
38+
// memberService.join("user3","12345678", "[email protected]");
39+
// memberService.join("user4","12345678", "[email protected]");
40+
// }
41+
42+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.back.global.rsData;
2+
3+
public record RsData<T>(int code, String message, T data) {
4+
5+
public static<T> RsData<T> of(int code, String message, T data) {
6+
return new RsData<>(code, message, data);
7+
}
8+
public static<T> RsData<T> of(int code, String message) {
9+
return new RsData<>(code,message, null);
10+
}
11+
12+
//성공 편의 메소드
13+
public static <T> RsData<T> successOf(T data) {
14+
return of(200,"success", data);
15+
}
16+
17+
//실패 편의 메소드
18+
public static <T> RsData<T> failOf(T data) {
19+
return of(500,"fail",data);
20+
}
21+
22+
// 실패 편의 메소드 (메시지 포함)
23+
public static <T> RsData<T> failOf(String message) {
24+
return of(500, message, null);
25+
}
26+
27+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
spring:
2+
# H2 Database 설정
3+
datasource:
4+
driver-class-name: org.h2.Driver
5+
url: jdbc:h2:./db_dev;MODE=MySQL
6+
username: sa
7+
password:
8+
9+
# H2 Console 설정
10+
h2:
11+
console: # H2 DB를 웹에서 관리할 수 있는 기능
12+
enabled: true # H2 Console 사용 여부
13+
path: /h2-console # H2 Console 접속 주소
14+
15+
# JPA 설정
16+
jpa:
17+
database-platform: org.hibernate.dialect.H2Dialect
18+
hibernate:
19+
ddl-auto: create-drop # 개발용: 시작할 때 테이블 생성, 종료할 때 삭제
20+
properties:
21+
hibernate:
22+
format_sql: true
23+
show_sql: true
24+
25+
# # AI 설정
26+
# ai:
27+
# openai:
28+
# chat:
29+
# options:
30+
# model: "gemini-2.0-flash"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
spring:
2+
datasource:
3+
url: jdbc:h2:mem:db_test;MODE=MySQL
4+
username: sa
5+
password:
6+
driver-class-name: org.h2.Driver

0 commit comments

Comments
 (0)