diff --git a/README.md b/README.md index 3401e87..a142501 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,14 @@ src/main/java/com/bio/bio_backend/ └── BioBackendApplication.java ``` -### 2. API 응답 표준화 (CustomApiResponse) +### 2. API 응답 표준화 (ApiResponseDto) #### 응답 구조 -모든 API 응답은 `CustomApiResponse` 형태로 표준화되어 있습니다. +모든 API 응답은 `ApiResponseDto` 형태로 표준화되어 있습니다. ```java -public class CustomApiResponse { +public class ApiResponseDto { private int code; // HTTP 상태 코드 private String message; // 응답 메시지 (ApiResponseCode enum 값) private String description; // 응답 설명 @@ -81,12 +81,12 @@ public class CustomApiResponse { ```java @PostMapping("/members") -public ResponseEntity> createMember(@RequestBody CreateMemberRequestDto requestDto) { +public ResponseEntity> createMember(@RequestBody CreateMemberRequestDto requestDto) { // ... 비즈니스 로직 ... // 성공 응답 - CustomApiResponse apiResponse = - CustomApiResponse.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto); + ApiResponseDto apiResponse = + ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto); return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } @@ -114,7 +114,7 @@ public enum ApiResponseCode { #### 핵심 규칙 -- **모든 API 응답**: `CustomApiResponse`로 감싸서 반환 +- **모든 API 응답**: `ApiResponseDto`로 감싸서 반환 - **공용 응답 코드**: `COMMON_` 접두사로 시작하는 범용 코드 사용 - **일관된 구조**: `code`, `message`, `description`, `data` 필드로 표준화 - **제네릭 활용**: ``를 통해 다양한 데이터 타입 지원 @@ -132,12 +132,11 @@ public enum ApiResponseCode { @Tag(name = "Member", description = "회원 관리 API") @Operation(summary = "회원 가입", description = "새로운 회원을 등록합니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "회원 가입 성공", - content = @Content(schema = @Schema(implementation = CustomApiResponse.class))), + @ApiResponse(responseCode = "201", description = "회원 가입 성공"), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", - content = @Content(schema = @Schema(implementation = CustomApiResponse.class))), + content = @Content(schema = @Schema(implementation = ApiResponseDto.class))), @ApiResponse(responseCode = "409", description = "중복된 사용자 정보", - content = @Content(schema = @Schema(implementation = CustomApiResponse.class))) + content = @Content(schema = @Schema(implementation = ApiResponseDto.class))) }) ``` diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java b/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java index f37dbb4..e1fd8e3 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java @@ -1,14 +1,11 @@ package com.bio.bio_backend.domain.user.member.controller; -import com.bio.bio_backend.global.dto.CustomApiResponse; +import com.bio.bio_backend.global.dto.ApiResponseDto; 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; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; import com.bio.bio_backend.domain.user.member.dto.MemberDto; @@ -27,6 +24,7 @@ import com.bio.bio_backend.global.utils.ApiResponseCode; @Tag(name = "Member", description = "회원 관련 API") @RestController +@RequestMapping("/members") @RequiredArgsConstructor @Slf4j public class MemberController { @@ -38,16 +36,15 @@ public class MemberController { @Operation(summary = "회원 가입", description = "새로운 회원을 등록합니다.") @ApiResponses({ @ApiResponse(responseCode = "201", description = "회원 가입 성공"), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"), - @ApiResponse(responseCode = "409", description = "중복된 사용자 정보") + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))), + @ApiResponse(responseCode = "409", description = "중복된 사용자 정보", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))) }) - @PostMapping("/members") - public ResponseEntity> createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) { + @PostMapping + public ResponseEntity> createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) { MemberDto member = memberMapper.toMemberDto(requestDto); MemberDto createdMember = memberService.createMember(member); CreateMemberResponseDto responseDto = memberMapper.toCreateMemberResponseDto(createdMember); - - CustomApiResponse apiResponse = CustomApiResponse.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto); + ApiResponseDto apiResponse = ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto); return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } @@ -81,7 +78,7 @@ public class MemberController { // } // @PutMapping("/member") - // public CustomApiResponse updateMember(@RequestBody @Valid CreateMemberRequestDTO requestMember, @AuthenticationPrincipal MemberDTO registrant) { + // public ApiResponseDto updateMember(@RequestBody @Valid CreateMemberRequestDTO requestMember, @AuthenticationPrincipal MemberDTO registrant) { // // 현재 JWT는 사용자 id 값을 통하여 생성, 회원정보 변경 시 JWT 재발급 여부 검토 // MemberDTO member = mapper.map(requestMember, MemberDTO.class); @@ -93,31 +90,31 @@ public class MemberController { // member.setRegSeq(registrant.getSeq()); // memberService.updateMember(member); - // return CustomApiResponse.success(ApiResponseCode.USER_INFO_CHANGE, null); + // return ApiResponseDto.success(ApiResponseCode.USER_INFO_CHANGE, null); // } // @DeleteMapping("/member") - // public CustomApiResponse deleteMember(@RequestBody @Valid CreateMemberRequestDTO requestMember){ + // public ApiResponseDto deleteMember(@RequestBody @Valid CreateMemberRequestDTO requestMember){ // MemberDTO member = mapper.map(requestMember, MemberDTO.class); // memberService.deleteMember(member); - // return CustomApiResponse.success(ApiResponseCode.USER_DELETE_SUCCESSFUL, null); + // return ApiResponseDto.success(ApiResponseCode.USER_DELETE_SUCCESSFUL, null); // } // @PostMapping("/logout") - // public CustomApiResponse logout(@AuthenticationPrincipal MemberDTO member) { + // public ApiResponseDto logout(@AuthenticationPrincipal MemberDTO member) { // String id = member.getId(); // try { // memberService.deleteRefreshToken(id); // } catch (Exception e) { - // return CustomApiResponse.fail(ApiResponseCode.INTERNAL_SERVER_ERROR, null); + // return ApiResponseDto.fail(ApiResponseCode.INTERNAL_SERVER_ERROR, null); // } - // return CustomApiResponse.success(ApiResponseCode.LOGOUT_SUCCESSFUL, null); + // return ApiResponseDto.success(ApiResponseCode.LOGOUT_SUCCESSFUL, null); // } } diff --git a/src/main/java/com/bio/bio_backend/global/config/SwaggerConfig.java b/src/main/java/com/bio/bio_backend/global/config/SwaggerConfig.java index 1ef3edb..4b5a186 100644 --- a/src/main/java/com/bio/bio_backend/global/config/SwaggerConfig.java +++ b/src/main/java/com/bio/bio_backend/global/config/SwaggerConfig.java @@ -2,7 +2,6 @@ package com.bio.bio_backend.global.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; @@ -22,10 +21,6 @@ public class SwaggerConfig { .version("v1.0.0") .license(new License() .name("STAM License") - .url("https://stam.kr/"))) - .servers(List.of( - new Server().url("http://localhost:8080/service").description("Local Development Server"), - new Server().url("https://api.bio.com/service").description("Production Server") - )); + .url("https://stam.kr/"))); } } diff --git a/src/main/java/com/bio/bio_backend/global/dto/CustomApiResponse.java b/src/main/java/com/bio/bio_backend/global/dto/ApiResponseDto.java similarity index 53% rename from src/main/java/com/bio/bio_backend/global/dto/CustomApiResponse.java rename to src/main/java/com/bio/bio_backend/global/dto/ApiResponseDto.java index 0634a8a..1d8e071 100644 --- a/src/main/java/com/bio/bio_backend/global/dto/CustomApiResponse.java +++ b/src/main/java/com/bio/bio_backend/global/dto/ApiResponseDto.java @@ -7,11 +7,10 @@ import com.bio.bio_backend.global.utils.ApiResponseCode; import lombok.Data; import lombok.RequiredArgsConstructor; -//공통 response Class @Data @RequiredArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) -public class CustomApiResponse { +public class ApiResponseDto { private int code; private String message; @@ -20,19 +19,19 @@ public class CustomApiResponse { private static final int SUCCESS = 200; - private CustomApiResponse(int code, String message, String description, T data){ + private ApiResponseDto(int code, String message, String description, T data){ this.code = code; this.message = message; this.description = description; this.data = data; } - public static CustomApiResponse success(ApiResponseCode responseCode, T data) { - return new CustomApiResponse(SUCCESS, responseCode.name(), responseCode.getDescription(), data); + public static ApiResponseDto success(ApiResponseCode responseCode, T data) { + return new ApiResponseDto(SUCCESS, responseCode.name(), responseCode.getDescription(), data); } - public static CustomApiResponse fail(ApiResponseCode responseCode, T data) { - return new CustomApiResponse(responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), data); + public static ApiResponseDto fail(ApiResponseCode responseCode, T data) { + return new ApiResponseDto(responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), data); } } diff --git a/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java b/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java index 0c52b84..01c54f5 100644 --- a/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java +++ b/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import com.bio.bio_backend.global.dto.CustomApiResponse; +import com.bio.bio_backend.global.dto.ApiResponseDto; import com.bio.bio_backend.global.utils.ApiResponseCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -36,14 +36,14 @@ public class CustomAuthenticationFailureHandler implements AuthenticationFailure response.setCharacterEncoding("UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); - CustomApiResponse apiResponse; + ApiResponseDto apiResponse; if (exception instanceof UsernameNotFoundException) { - apiResponse = CustomApiResponse.fail(ApiResponseCode.USER_NOT_FOUND, null); + apiResponse = ApiResponseDto.fail(ApiResponseCode.USER_NOT_FOUND, null); } else if (exception instanceof BadCredentialsException) { - apiResponse = CustomApiResponse.fail(ApiResponseCode.COMMON_UNAUTHORIZED, null); + apiResponse = ApiResponseDto.fail(ApiResponseCode.COMMON_UNAUTHORIZED, null); } else { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - apiResponse = CustomApiResponse.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR, null); + apiResponse = ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR, null); } String jsonResponse = objectMapper.writeValueAsString(apiResponse); diff --git a/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java b/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java index 2bba0c7..9707760 100644 --- a/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java @@ -1,92 +1,32 @@ 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; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.servlet.resource.NoResourceFoundException; -import com.fasterxml.jackson.core.JsonProcessingException; - -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.security.SignatureException; -import com.bio.bio_backend.global.dto.CustomApiResponse; +import com.bio.bio_backend.global.dto.ApiResponseDto; import com.bio.bio_backend.global.utils.ApiResponseCode; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(HttpMessageNotReadableException.class) - public CustomApiResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { - if (Objects.requireNonNull(e.getMessage()).contains("JSON parse error")) { - return CustomApiResponse.fail(ApiResponseCode.COMMON_FORMAT_WRONG, null); - } - return CustomApiResponse.fail(ApiResponseCode.COMMON_BAD_REQUEST, null); - } - @ExceptionHandler(HttpRequestMethodNotSupportedException.class) - public CustomApiResponse handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { - return CustomApiResponse.fail(ApiResponseCode.COMMON_METHOD_NOT_ALLOWED, null); - } - - @ExceptionHandler(NoResourceFoundException.class) - public CustomApiResponse handleNoResourceFoundException(NoResourceFoundException e){ - return CustomApiResponse.fail(ApiResponseCode.COMMON_NOT_FOUND, null); - } - - @ExceptionHandler(IllegalArgumentException.class) - public CustomApiResponse handleIllegalArgumentException(IllegalArgumentException e){ - return CustomApiResponse.fail(ApiResponseCode.COMMON_ARGUMENT_NOT_VALID, null); - } - - @ExceptionHandler(IndexOutOfBoundsException.class) - public CustomApiResponse handleIndexOutOfBoundsException(IndexOutOfBoundsException e){ - return CustomApiResponse.fail(ApiResponseCode.COMMON_INDEX_OUT_OF_BOUND, null); - } - - @ExceptionHandler(SignatureException.class) - public CustomApiResponse handleSignatureException(SignatureException e) { - return CustomApiResponse.fail(ApiResponseCode.JWT_SIGNATURE_MISMATCH, null); - } - - @ExceptionHandler(MalformedJwtException.class) - public CustomApiResponse handleMalformedJwtException(MalformedJwtException e) { - return CustomApiResponse.fail(ApiResponseCode.JWT_SIGNATURE_MISMATCH, null); - } - - @ExceptionHandler(JwtException.class) - public CustomApiResponse handleJwtExceptionException(JwtException e) { - return CustomApiResponse.fail(ApiResponseCode.JWT_TOKEN_NULL, null); - } - - @ExceptionHandler(ExpiredJwtException.class) - public CustomApiResponse handleExpiredJwtException(ExpiredJwtException e) { - return CustomApiResponse.fail(ApiResponseCode.JWT_TOKEN_EXPIRED, null); - } - - @ExceptionHandler(JsonProcessingException.class) - public CustomApiResponse handleExpiredJwtException(JsonProcessingException e) { - return CustomApiResponse.fail(ApiResponseCode.COMMON_JSON_PROCESSING_EXCEPTION, null); - } - @ExceptionHandler(ApiException.class) - public CustomApiResponse handleApiException(ApiException e) { - return CustomApiResponse.fail(e.getResponseCode(), null); + public ResponseEntity> handleApiException(ApiException e) { + ApiResponseDto response = ApiResponseDto.fail(e.getResponseCode(), null); + return ResponseEntity.status(e.getResponseCode().getStatusCode()).body(response); } @ExceptionHandler(MethodArgumentNotValidException.class) - public CustomApiResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + public ResponseEntity> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { // 검증 실패한 필드들의 상세 오류 정보 추출 var errors = e.getBindingResult().getFieldErrors().stream() .map(error -> new ValidationError(error.getField(), error.getDefaultMessage())) .toList(); - return CustomApiResponse.fail(ApiResponseCode.COMMON_ARGUMENT_NOT_VALID, errors); + ApiResponseDto response = ApiResponseDto.fail(ApiResponseCode.COMMON_ARGUMENT_NOT_VALID, errors); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } // 검증 오류 상세 정보를 위한 내부 클래스 diff --git a/src/main/java/com/bio/bio_backend/global/exception/JwtAccessDeniedHandler.java b/src/main/java/com/bio/bio_backend/global/exception/JwtAccessDeniedHandler.java index dfe92f9..bcb1a23 100644 --- a/src/main/java/com/bio/bio_backend/global/exception/JwtAccessDeniedHandler.java +++ b/src/main/java/com/bio/bio_backend/global/exception/JwtAccessDeniedHandler.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import com.bio.bio_backend.global.dto.CustomApiResponse; +import com.bio.bio_backend.global.dto.ApiResponseDto; import com.bio.bio_backend.global.utils.ApiResponseCode; @Component @@ -35,8 +35,7 @@ public class JwtAccessDeniedHandler implements AccessDeniedHandler { } else { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType(MediaType.APPLICATION_JSON_VALUE); - new ObjectMapper().writeValue(response.getWriter(), - CustomApiResponse.fail(ApiResponseCode.COMMON_FORBIDDEN, null)); + new ObjectMapper().writeValue(response.getWriter(), ApiResponseDto.fail(ApiResponseCode.COMMON_FORBIDDEN, null)); } } } diff --git a/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java b/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java index 84df2b9..9a5caab 100644 --- a/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java @@ -6,7 +6,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import com.bio.bio_backend.global.dto.CustomApiResponse; +import com.bio.bio_backend.global.dto.ApiResponseDto; import com.bio.bio_backend.domain.user.member.dto.LoginRequestDto; import com.bio.bio_backend.domain.user.member.dto.LoginResponseDto; import com.bio.bio_backend.domain.user.member.dto.MemberDto; @@ -116,6 +116,6 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getWriter(), - CustomApiResponse.success(ApiResponseCode.LOGIN_SUCCESSFUL, memberData)); + ApiResponseDto.success(ApiResponseCode.LOGIN_SUCCESSFUL, memberData)); } } diff --git a/src/main/java/com/bio/bio_backend/global/security/JwtTokenFilter.java b/src/main/java/com/bio/bio_backend/global/security/JwtTokenFilter.java index 6f054f7..92afe3b 100644 --- a/src/main/java/com/bio/bio_backend/global/security/JwtTokenFilter.java +++ b/src/main/java/com/bio/bio_backend/global/security/JwtTokenFilter.java @@ -20,7 +20,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import com.bio.bio_backend.global.dto.CustomApiResponse; +import com.bio.bio_backend.global.dto.ApiResponseDto; import com.bio.bio_backend.domain.user.member.service.MemberService; import com.bio.bio_backend.global.utils.ApiResponseCode; import com.bio.bio_backend.global.utils.JwtUtils; @@ -50,7 +50,7 @@ public class JwtTokenFilter extends OncePerRequestFilter { String refreshToken = jwtUtils.extractRefreshJwtFromCookie(request); if(accessToken == null){ - sendJsonResponse(response, CustomApiResponse.fail(ApiResponseCode.JWT_TOKEN_NULL, null)); + sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_NULL, null)); return; } @@ -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, ApiResponseDto.fail(ApiResponseCode.ALL_TOKEN_INVALID, null)); } } catch (Exception e) { request.setAttribute("exception", e); @@ -92,7 +92,7 @@ public class JwtTokenFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } - private void sendJsonResponse(HttpServletResponse response, CustomApiResponse apiResponse) throws IOException { + private void sendJsonResponse(HttpServletResponse response, ApiResponseDto apiResponse) throws IOException { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); response.setStatus(apiResponse.getCode());