Skip to content

Commit 0eee885

Browse files
authored
Merge pull request #30 from Nexters/feat/25-global-exception
feat: 전역 예외처리 및 공통 ApiResult 구현
2 parents 22398c1 + a2d9f97 commit 0eee885

File tree

15 files changed

+368
-37
lines changed

15 files changed

+368
-37
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ RUN gradle clean build -x test --no-daemon
99
FROM openjdk:17-jdk-slim
1010
WORKDIR /app
1111
COPY --from=build /app/build/libs/*SNAPSHOT.jar project.jar
12-
EXPOSE 8080
12+
EXPOSE 9090
1313
ENTRYPOINT ["java", "-jar", "project.jar"]

docker-compose.dev.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ services:
2626
- JWT_SECRET_KEY=${JWT_SECRET_KEY}
2727
- APP_OAUTH2_AUTHORIZED_REDIRECT_URI=${OAUTH_REDIRECT_URI}
2828
ports:
29-
- 9090:8080
29+
- "9090:9090"
3030
depends_on:
3131
db:
3232
condition: service_healthy

docker-compose.prod.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ services:
2626
- JWT_SECRET_KEY=${JWT_SECRET_KEY}
2727
- APP_OAUTH2_AUTHORIZED_REDIRECT_URI=${OAUTH_REDIRECT_URI}
2828
ports:
29-
- 9090:8080
29+
- "9090:9090"
3030
depends_on:
3131
db:
3232
condition: service_healthy

src/main/java/com/climbup/climbup/auth/controller/OnboardingController.java

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
import com.climbup.climbup.auth.dto.OnboardingDto;
44
import com.climbup.climbup.auth.service.OnboardingService;
55
import com.climbup.climbup.auth.util.JwtUtil;
6+
import com.climbup.climbup.common.dto.ApiResult;
67
import com.climbup.climbup.user.docs.UserApiDocs;
78
import io.swagger.v3.oas.annotations.Operation;
89
import io.swagger.v3.oas.annotations.Parameter;
10+
import io.swagger.v3.oas.annotations.media.Content;
11+
import io.swagger.v3.oas.annotations.media.ExampleObject;
912
import io.swagger.v3.oas.annotations.responses.ApiResponse;
13+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1014
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
1115
import io.swagger.v3.oas.annotations.tags.Tag;
1216
import lombok.RequiredArgsConstructor;
@@ -27,9 +31,28 @@ public class OnboardingController {
2731
description = "암장과 레벨을 동시에 설정하여 온보딩을 완료합니다.",
2832
security = @SecurityRequirement(name = "bearerAuth")
2933
)
30-
@ApiResponse(responseCode = "200", description = "온보딩 완료 성공")
34+
@ApiResponses({
35+
@ApiResponse(responseCode = "200", description = "온보딩 완료 성공"),
36+
@ApiResponse(
37+
responseCode = "400",
38+
description = "이미 온보딩 완료된 사용자",
39+
content = @Content(
40+
mediaType = "application/json",
41+
examples = @ExampleObject(
42+
value = """
43+
{
44+
"errorCode": "USER_001",
45+
"message": "이미 온보딩을 완료한 사용자입니다.",
46+
"timestamp": "2024-01-15T10:30:00",
47+
"path": "/api/onboarding/complete"
48+
}
49+
"""
50+
)
51+
)
52+
)
53+
})
3154
@PostMapping("/complete")
32-
public ResponseEntity<OnboardingDto.Response> completeOnboarding(
55+
public ResponseEntity<ApiResult<OnboardingDto.Response>> completeOnboarding(
3356
@Parameter(
3457
description = UserApiDocs.AUTHORIZATION_DESCRIPTION,
3558
required = true,
@@ -43,17 +66,36 @@ public ResponseEntity<OnboardingDto.Response> completeOnboarding(
4366

4467
onboardingService.completeOnboarding(userId, request.getGymId(), request.getLevelId());
4568

46-
return ResponseEntity.ok(new OnboardingDto.Response("온보딩이 완료되었습니다."));
69+
return ResponseEntity.ok(ApiResult.success(new OnboardingDto.Response("온보딩이 완료되었습니다.")));
4770
}
4871

4972
@Operation(
5073
summary = "암장 선택",
5174
description = "사용자의 암장을 설정합니다.",
5275
security = @SecurityRequirement(name = "bearerAuth")
5376
)
54-
@ApiResponse(responseCode = "200", description = "암장 설정 성공")
77+
@ApiResponses({
78+
@ApiResponse(responseCode = "200", description = "암장 설정 성공"),
79+
@ApiResponse(
80+
responseCode = "400",
81+
description = "이미 온보딩 완료된 사용자",
82+
content = @Content(
83+
mediaType = "application/json",
84+
examples = @ExampleObject(
85+
value = """
86+
{
87+
"errorCode": "USER_001",
88+
"message": "이미 온보딩을 완료한 사용자입니다.",
89+
"timestamp": "2024-01-15T10:30:00",
90+
"path": "/api/onboarding/gym"
91+
}
92+
"""
93+
)
94+
)
95+
)
96+
})
5597
@PostMapping("/gym")
56-
public ResponseEntity<OnboardingDto.Response> setGym(
98+
public ResponseEntity<ApiResult<OnboardingDto.Response>> setGym(
5799
@Parameter(
58100
description = UserApiDocs.AUTHORIZATION_DESCRIPTION,
59101
required = true,
@@ -67,17 +109,36 @@ public ResponseEntity<OnboardingDto.Response> setGym(
67109

68110
onboardingService.setUserGym(userId, request.getGymId());
69111

70-
return ResponseEntity.ok(new OnboardingDto.Response("암장이 설정되었습니다."));
112+
return ResponseEntity.ok(ApiResult.success(new OnboardingDto.Response("암장이 설정되었습니다.")));
71113
}
72114

73115
@Operation(
74116
summary = "레벨 선택",
75117
description = "사용자의 레벨을 설정합니다.",
76118
security = @SecurityRequirement(name = "bearerAuth")
77119
)
78-
@ApiResponse(responseCode = "200", description = "레벨 설정 성공")
120+
@ApiResponses({
121+
@ApiResponse(responseCode = "200", description = "레벨 설정 성공"),
122+
@ApiResponse(
123+
responseCode = "400",
124+
description = "이미 온보딩 완료된 사용자",
125+
content = @Content(
126+
mediaType = "application/json",
127+
examples = @ExampleObject(
128+
value = """
129+
{
130+
"errorCode": "USER_001",
131+
"message": "이미 온보딩을 완료한 사용자입니다.",
132+
"timestamp": "2024-01-15T10:30:00",
133+
"path": "/api/onboarding/level"
134+
}
135+
"""
136+
)
137+
)
138+
)
139+
})
79140
@PostMapping("/level")
80-
public ResponseEntity<OnboardingDto.Response> setLevel(
141+
public ResponseEntity<ApiResult<OnboardingDto.Response>> setLevel(
81142
@Parameter(
82143
description = UserApiDocs.AUTHORIZATION_DESCRIPTION,
83144
required = true,
@@ -91,6 +152,6 @@ public ResponseEntity<OnboardingDto.Response> setLevel(
91152

92153
onboardingService.setUserLevel(userId, request.getLevelId());
93154

94-
return ResponseEntity.ok(new OnboardingDto.Response("레벨이 설정되었습니다."));
155+
return ResponseEntity.ok(ApiResult.success(new OnboardingDto.Response("레벨이 설정되었습니다.")));
95156
}
96157
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.climbup.climbup.common.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
7+
@Data
8+
@Builder
9+
@Schema(description = "Api 응답 정보")
10+
public class ApiResult<T> {
11+
12+
@Schema(description = "응답 메시지", example = "요청이 성공적으로 처리되었습니다.")
13+
private String message;
14+
15+
@Schema(description = "응답 데이터")
16+
private T data;
17+
18+
public static <T> ApiResult<T> success(T data) {
19+
return ApiResult.<T>builder()
20+
.message("요청이 성공적으로 처리되었습니다.")
21+
.data(data)
22+
.build();
23+
}
24+
25+
public static <T> ApiResult<T> success(String message, T data) {
26+
return ApiResult.<T>builder()
27+
.message(message)
28+
.data(data)
29+
.build();
30+
}
31+
32+
public static ApiResult<Void> success() {
33+
return ApiResult.<Void>builder()
34+
.message("요청이 성공적으로 처리되었습니다.")
35+
.build();
36+
}
37+
38+
public static ApiResult<Void> success(String message) {
39+
return ApiResult.<Void>builder()
40+
.message(message)
41+
.build();
42+
}
43+
44+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.climbup.climbup.common.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.Builder;
6+
import lombok.Data;
7+
8+
import java.time.LocalDateTime;
9+
10+
@Data
11+
@Builder
12+
@Schema(description = "에러 응답 정보")
13+
public class ErrorResponse {
14+
15+
@Schema(description = "에러 코드", example = "USER_001")
16+
private String errorCode;
17+
18+
@Schema(description = "에러 메시지", example = "이미 온보딩을 완료한 사용자입니다.")
19+
private String message;
20+
21+
@Schema(description = "에러 발생 시간", example = "2024-01-01T12:00:00")
22+
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
23+
private LocalDateTime timestamp;
24+
25+
@Schema(description = "요청 경로", example = "/api/users/onboarding")
26+
private String path;
27+
28+
public static ErrorResponse of(String errorCode, String message, String path) {
29+
return ErrorResponse.builder()
30+
.errorCode(errorCode)
31+
.message(message)
32+
.timestamp(LocalDateTime.now())
33+
.path(path)
34+
.build();
35+
}
36+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.climbup.climbup.common.exception;
2+
3+
import lombok.Getter;
4+
import org.springframework.http.HttpStatus;
5+
6+
@Getter
7+
public abstract class BusinessException extends RuntimeException {
8+
9+
private final String errorCode;
10+
private final HttpStatus httpStatus;
11+
12+
protected BusinessException(String errorCode, String message, HttpStatus httpStatus) {
13+
super(message);
14+
this.errorCode = errorCode;
15+
this.httpStatus = httpStatus;
16+
}
17+
}

0 commit comments

Comments
 (0)