[API 응답 표준화]
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.bio.bio_backend.domain.user.member.controller;
|
||||
|
||||
import com.bio.bio_backend.global.dto.CustomApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
@@ -22,8 +23,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import com.bio.bio_backend.global.utils.ApiResponseCode;
|
||||
|
||||
|
||||
@Tag(name = "Member", description = "회원 관련 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@@ -33,19 +35,21 @@ public class MemberController {
|
||||
private final MemberMapper memberMapper;
|
||||
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
|
||||
@Operation(summary = "회원 가입", description = "새로운 회원을 등록합니다.", tags = {"Member"})
|
||||
@Operation(summary = "회원 가입", description = "새로운 회원을 등록합니다.")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "201", description = "회원 가입 성공", content = @Content(schema = @Schema(implementation = CreateMemberResponseDto.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(schema = @Schema(implementation = CustomApiResponse.class))),
|
||||
@ApiResponse(responseCode = "409", description = "중복된 사용자 정보", content = @Content(schema = @Schema(implementation = CustomApiResponse.class)))
|
||||
@ApiResponse(responseCode = "201", description = "회원 가입 성공"),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"),
|
||||
@ApiResponse(responseCode = "409", description = "중복된 사용자 정보")
|
||||
})
|
||||
@PostMapping("/members")
|
||||
public ResponseEntity<CreateMemberResponseDto> createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) {
|
||||
public ResponseEntity<CustomApiResponse<CreateMemberResponseDto>> createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) {
|
||||
MemberDto member = memberMapper.toMemberDto(requestDto);
|
||||
MemberDto createdMember = memberService.createMember(member);
|
||||
CreateMemberResponseDto responseDto = memberMapper.toCreateMemberResponseDto(createdMember);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
|
||||
CustomApiResponse<CreateMemberResponseDto> apiResponse = CustomApiResponse.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse);
|
||||
}
|
||||
|
||||
// @PostMapping("/member/list")
|
||||
|
@@ -40,10 +40,10 @@ public class CustomAuthenticationFailureHandler implements AuthenticationFailure
|
||||
if (exception instanceof UsernameNotFoundException) {
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.USER_NOT_FOUND, null);
|
||||
} else if (exception instanceof BadCredentialsException) {
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.AUTHENTICATION_FAILED, null);
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.COMMON_UNAUTHORIZED, null);
|
||||
} else {
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.INTERNAL_SERVER_ERROR, null);
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR, null);
|
||||
}
|
||||
|
||||
String jsonResponse = objectMapper.writeValueAsString(apiResponse);
|
||||
|
@@ -2,6 +2,7 @@ package com.bio.bio_backend.global.exception;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
@@ -17,7 +18,6 @@ import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import com.bio.bio_backend.global.dto.CustomApiResponse;
|
||||
import com.bio.bio_backend.global.utils.ApiResponseCode;
|
||||
import com.bio.bio_backend.domain.user.member.exception.UserDuplicateException;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
@@ -41,12 +41,12 @@ public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public CustomApiResponse<Void> handleIllegalArgumentException(IllegalArgumentException e){
|
||||
return CustomApiResponse.fail(ApiResponseCode.ARGUMENT_NOT_VALID, null);
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_ARGUMENT_NOT_VALID, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(IndexOutOfBoundsException.class)
|
||||
public CustomApiResponse<Void> handleIndexOutOfBoundsException(IndexOutOfBoundsException e){
|
||||
return CustomApiResponse.fail(ApiResponseCode.INDEX_OUT_OF_BOUND, null);
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_INDEX_OUT_OF_BOUND, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(SignatureException.class)
|
||||
@@ -71,7 +71,7 @@ public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(JsonProcessingException.class)
|
||||
public CustomApiResponse<Void> handleExpiredJwtException(JsonProcessingException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.JSON_PROCESSING_EXCEPTION, null);
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_JSON_PROCESSING_EXCEPTION, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ApiException.class)
|
||||
@@ -86,20 +86,9 @@ public class GlobalExceptionHandler {
|
||||
.map(error -> new ValidationError(error.getField(), error.getDefaultMessage()))
|
||||
.toList();
|
||||
|
||||
return CustomApiResponse.fail(ApiResponseCode.ARGUMENT_NOT_VALID, errors);
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_ARGUMENT_NOT_VALID, errors);
|
||||
}
|
||||
|
||||
|
||||
// 검증 오류 상세 정보를 위한 내부 클래스
|
||||
private static class ValidationError {
|
||||
private final String field;
|
||||
private final String message;
|
||||
|
||||
public ValidationError(String field, String message) {
|
||||
this.field = field;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getField() { return field; }
|
||||
public String getMessage() { return message; }
|
||||
}
|
||||
private record ValidationError(String field, String message) { }
|
||||
}
|
||||
|
@@ -83,7 +83,7 @@ public class JwtTokenFilter extends OncePerRequestFilter {
|
||||
response.setHeader("Authorization", "Bearer " + newAccessToken);
|
||||
filterChain.doFilter(request, response);
|
||||
} else {
|
||||
sendJsonResponse(response, CustomApiResponse.fail(ApiResponseCode.All_TOKEN_INVALID, null));
|
||||
sendJsonResponse(response, CustomApiResponse.fail(ApiResponseCode.ALL_TOKEN_INVALID, null));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
request.setAttribute("exception", e);
|
||||
|
@@ -12,43 +12,58 @@ import org.springframework.http.HttpStatus;
|
||||
@AllArgsConstructor
|
||||
public enum ApiResponseCode {
|
||||
|
||||
// 200 OK
|
||||
LOGIN_SUCCESSFUL(HttpStatus.OK.value(), "Login successful"),
|
||||
LOGOUT_SUCCESSFUL(HttpStatus.OK.value(), "Logout successful"),
|
||||
USER_INFO_CHANGE(HttpStatus.OK.value(), "User info update successful"),
|
||||
USER_DELETE_SUCCESSFUL(HttpStatus.OK.value(), "User delete is successful"),
|
||||
|
||||
// 409 Conflict
|
||||
USER_ID_DUPLICATE(HttpStatus.CONFLICT.value(), "User ID already exists"),
|
||||
|
||||
/*공통 Code*/
|
||||
// 200 OK
|
||||
COMMON_SUCCESS(HttpStatus.OK.value(), "요청 성공"),
|
||||
COMMON_SUCCESS_CREATED(HttpStatus.CREATED.value(), "성공적으로 생성되었습니다"),
|
||||
COMMON_SUCCESS_UPDATED(HttpStatus.OK.value(), "성공적으로 수정되었습니다"),
|
||||
COMMON_SUCCESS_DELETED(HttpStatus.OK.value(), "성공적으로 삭제되었습니다"),
|
||||
COMMON_SUCCESS_RETRIEVED(HttpStatus.OK.value(), "성공적으로 조회되었습니다"),
|
||||
|
||||
// 400 Bad Request
|
||||
COMMON_BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "Required request body is missing or Error"),
|
||||
COMMON_FORMAT_WRONG(HttpStatus.BAD_REQUEST.value(), "Request format is incorrect"),
|
||||
ARGUMENT_NOT_VALID(HttpStatus.BAD_REQUEST.value(), "Argument is not valid"),
|
||||
COMMON_BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "필수 요청 본문이 누락되었거나 오류가 발생했습니다"),
|
||||
COMMON_FORMAT_WRONG(HttpStatus.BAD_REQUEST.value(), "요청 형식이 올바르지 않습니다"),
|
||||
COMMON_ARGUMENT_NOT_VALID(HttpStatus.BAD_REQUEST.value(), "인자가 유효하지 않습니다"),
|
||||
|
||||
// 401 Unauthorized
|
||||
COMMON_UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"),
|
||||
USER_NOT_FOUND(HttpStatus.UNAUTHORIZED.value(), "User not found. Authentication failed"),
|
||||
AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED.value(), "Password is invalid"),
|
||||
JWT_SIGNATURE_MISMATCH(HttpStatus.UNAUTHORIZED.value(), "JWT signature does not match. Authentication failed"),
|
||||
JWT_TOKEN_NULL(HttpStatus.UNAUTHORIZED.value(), "JWT token is null"),
|
||||
JWT_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "Token is Expired"),
|
||||
All_TOKEN_INVALID(HttpStatus.UNAUTHORIZED.value(), "Access and Refresh tokens are expired or invalid"),
|
||||
COMMON_UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "인증에 실패했습니다"),
|
||||
|
||||
// 403 Forbidden
|
||||
COMMON_FORBIDDEN(HttpStatus.FORBIDDEN.value(), "Access is denied"),
|
||||
COMMON_FORBIDDEN(HttpStatus.FORBIDDEN.value(), "접근이 거부되었습니다"),
|
||||
|
||||
// 404 Not Found
|
||||
COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "Resource is not found"),
|
||||
COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "리소스를 찾을 수 없습니다"),
|
||||
|
||||
// 405 Method Not Allowed
|
||||
COMMON_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "Method not Allowed"),
|
||||
COMMON_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "허용되지 않는 메소드입니다"),
|
||||
|
||||
// 409 Conflict
|
||||
COMMON_CONFLICT(HttpStatus.CONFLICT.value(), "충돌이 발생했습니다"),
|
||||
|
||||
// 500 Internal Server Error
|
||||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An error occurred on the server"),
|
||||
INDEX_OUT_OF_BOUND(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Index out of bounds for length"),
|
||||
JSON_PROCESSING_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Check if it is a valid JSON format");
|
||||
COMMON_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "서버에서 오류가 발생했습니다"),
|
||||
COMMON_INDEX_OUT_OF_BOUND(HttpStatus.INTERNAL_SERVER_ERROR.value(), "인덱스가 범위를 벗어났습니다"),
|
||||
COMMON_JSON_PROCESSING_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR.value(), "유효한 JSON 형식인지 확인해주세요"),
|
||||
|
||||
/*사용자 관련 Code*/
|
||||
// 200 OK
|
||||
LOGIN_SUCCESSFUL(HttpStatus.OK.value(), "로그인에 성공했습니다"),
|
||||
LOGOUT_SUCCESSFUL(HttpStatus.OK.value(), "로그아웃에 성공했습니다"),
|
||||
USER_INFO_CHANGE(HttpStatus.OK.value(), "사용자 정보가 성공적으로 수정되었습니다"),
|
||||
USER_DELETE_SUCCESSFUL(HttpStatus.OK.value(), "사용자가 성공적으로 삭제되었습니다"),
|
||||
|
||||
// 409 Conflict
|
||||
USER_ID_DUPLICATE(HttpStatus.CONFLICT.value(), "이미 존재하는 사용자 ID입니다"),
|
||||
|
||||
// 401 Unauthorized
|
||||
USER_NOT_FOUND(HttpStatus.UNAUTHORIZED.value(), "사용자를 찾을 수 없습니다. 인증에 실패했습니다"),
|
||||
USER_PASSWORD_INVALID(HttpStatus.UNAUTHORIZED.value(), "비밀번호가 올바르지 않습니다"),
|
||||
|
||||
// JWT 관련
|
||||
JWT_SIGNATURE_MISMATCH(HttpStatus.UNAUTHORIZED.value(), "JWT 서명이 일치하지 않습니다. 인증에 실패했습니다"),
|
||||
JWT_TOKEN_NULL(HttpStatus.UNAUTHORIZED.value(), "JWT 토큰이 null입니다"),
|
||||
JWT_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "토큰이 만료되었습니다"),
|
||||
ALL_TOKEN_INVALID(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰과 리프레시 토큰이 모두 만료되었거나 유효하지 않습니다");
|
||||
|
||||
private final int statusCode;
|
||||
private final String description;
|
||||
|
Reference in New Issue
Block a user