-
Notifications
You must be signed in to change notification settings - Fork 170
[4기 - 황창현] Week 3-2 REST API로 바우처 관리 구현, RestControllerAdvice를 통한 예외 처리 #845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: changhyeon/w3-1
Are you sure you want to change the base?
Changes from all commits
f0b4c04
2f36332
71137a2
c033680
9896465
389b423
583b133
2ebb955
fa56608
6e8dd1a
5488b63
2d8a83a
1f885b1
fa951d0
51f51b3
686db55
3655a01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package com.programmers.springweekly.controller; | ||
|
|
||
| import com.programmers.springweekly.dto.customer.request.CustomerCreateRequest; | ||
| import com.programmers.springweekly.dto.customer.response.CustomerListResponse; | ||
| import com.programmers.springweekly.dto.customer.response.CustomerResponse; | ||
| import com.programmers.springweekly.service.CustomerService; | ||
| import com.programmers.springweekly.util.validator.CustomerValidator; | ||
| import java.util.List; | ||
| import java.util.NoSuchElementException; | ||
| import java.util.UUID; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.DeleteMapping; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/customers") | ||
| @RequiredArgsConstructor | ||
| public class CustomerApiController { | ||
|
|
||
| private final CustomerService customerService; | ||
|
|
||
| @PostMapping | ||
| public ResponseEntity<CustomerResponse> save(@Validated CustomerCreateRequest customerCreateRequest) { | ||
| CustomerValidator.validateCustomer( | ||
| customerCreateRequest.getCustomerName(), | ||
| customerCreateRequest.getCustomerEmail(), | ||
| customerCreateRequest.getCustomerType() | ||
| ); | ||
|
|
||
| CustomerResponse customerResponse = customerService.save(customerCreateRequest); | ||
|
|
||
| return ResponseEntity.status(HttpStatus.CREATED).body(customerResponse); | ||
| } | ||
|
|
||
| @GetMapping | ||
| public ResponseEntity<List<CustomerResponse>> getFindAll() { | ||
| CustomerListResponse customerListResponse = customerService.findAll(); | ||
|
|
||
| return ResponseEntity.ok(customerListResponse.getCustomerList()); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| public ResponseEntity<CustomerResponse> findById(@PathVariable("id") UUID customerId) { | ||
| CustomerResponse customerResponse = customerService.findById(customerId); | ||
|
|
||
| return ResponseEntity.ok(customerResponse); | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| public ResponseEntity<Void> deleteById(@PathVariable("id") UUID customerId) { | ||
| boolean isExistCustomerId = customerService.existById(customerId); | ||
|
|
||
| if (!isExistCustomerId) { | ||
| throw new NoSuchElementException("사용자가 삭제하려는 아이디 " + customerId + "는 없는 ID입니다."); | ||
| } | ||
|
|
||
| customerService.deleteById(customerId); | ||
|
|
||
| return ResponseEntity.noContent().build(); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| package com.programmers.springweekly.controller; | ||
|
|
||
| import com.programmers.springweekly.dto.voucher.request.VoucherCreateRequest; | ||
| import com.programmers.springweekly.dto.voucher.response.VoucherListResponse; | ||
| import com.programmers.springweekly.dto.voucher.response.VoucherResponse; | ||
| import com.programmers.springweekly.service.VoucherService; | ||
| import com.programmers.springweekly.util.validator.VoucherValidator; | ||
| import java.util.List; | ||
| import java.util.NoSuchElementException; | ||
| import java.util.UUID; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.DeleteMapping; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/vouchers") | ||
| @RequiredArgsConstructor | ||
| public class VoucherApiController { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. customer 쪽과 동일하게 리팩토링해주세요~~
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀하신대로 동일하게 반영했습니다! |
||
|
|
||
| private final VoucherService voucherService; | ||
|
|
||
| @PostMapping | ||
| public ResponseEntity<VoucherResponse> save(@Validated VoucherCreateRequest voucherCreateRequest) { | ||
| VoucherValidator.validateVoucher( | ||
| voucherCreateRequest.getVoucherType(), | ||
| String.valueOf(voucherCreateRequest.getDiscountAmount()) | ||
| ); | ||
|
|
||
| VoucherResponse voucherResponse = voucherService.save(voucherCreateRequest); | ||
|
|
||
| return ResponseEntity.status(HttpStatus.CREATED).body(voucherResponse); | ||
| } | ||
|
|
||
| @GetMapping | ||
| public ResponseEntity<List<VoucherResponse>> getFindAll() { | ||
| VoucherListResponse voucherListResponse = voucherService.findAll(); | ||
|
|
||
| return ResponseEntity.ok(voucherListResponse.getVoucherList()); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| public ResponseEntity<VoucherResponse> findById(@PathVariable("id") UUID voucherId) { | ||
| VoucherResponse voucherResponse = voucherService.findById(voucherId); | ||
|
|
||
| return ResponseEntity.ok(voucherResponse); | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| public ResponseEntity<Void> deleteById(@PathVariable("id") UUID voucherId) { | ||
| boolean isExistVoucherId = voucherService.existById(voucherId); | ||
|
|
||
| if (!isExistVoucherId) { | ||
| throw new NoSuchElementException("사용자가 삭제하려는 바우처 " + voucherId + "는 없는 ID입니다."); | ||
| } | ||
|
|
||
| voucherService.deleteById(voucherId); | ||
|
|
||
| return ResponseEntity.noContent().build(); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.programmers.springweekly.dto; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public class ErrorResponse { | ||
|
|
||
| private final int errorCode; | ||
| private final String errorMsg; | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,7 @@ | |
| import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
|
||
| @Slf4j | ||
| @ControllerAdvice | ||
| // @ControllerAdvice | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RestControllerAdvice에서도 동일한 클래스에 대한 예외 처리를 하고 있어서 @ControllerAdvice를 주석처리하여 사용하지 않도록 했습니다! 동일한 클래스에 대해서 ControllerAdvice와 RestControllerAdvice 2개를 동시에 사용할 수 있는지 확인해보았을 때 basePackage 설정을 통해 할 수는 있었으나 현재 디렉토리 구조성 불가하다고 판단되어 주석처리로 미사용 상태로 두었습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ControllerAdvice와 RestControllerAdvice 차이점은 무엇일까요?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ControllerAdvice는 에러처리 후 반환 값으로 ViewResolver를 통해 페이지를 반환하지만 RestControllerAdvice는 ResponseBody를 이용해 객체를 반환합니다! Controller와 RestController처럼 Rest 붙고 안붙고의 차이는 그래서 위 처럼 생각했을 때 RestController API로 작동하는 URI의 경우 RestControllerAdvice를 통해 동작할 것이고 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇다면 제거?ㅎㅎ |
||
| public class GlobalExceptionHandler { | ||
|
|
||
| private static final String ERROR_MSG = "errorMsg"; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||
| package com.programmers.springweekly.exception; | ||||||
|
|
||||||
| import com.programmers.springweekly.dto.ErrorResponse; | ||||||
| import java.util.NoSuchElementException; | ||||||
| import lombok.extern.slf4j.Slf4j; | ||||||
| import org.springframework.dao.DataAccessException; | ||||||
| import org.springframework.dao.DuplicateKeyException; | ||||||
| import org.springframework.http.HttpStatus; | ||||||
| import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||||||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||||||
|
|
||||||
| @Slf4j | ||||||
| @RestControllerAdvice | ||||||
| public class RestGlobalExceptionHandler { | ||||||
|
|
||||||
| @ExceptionHandler(NoSuchElementException.class) | ||||||
| public ResponseEntity<ErrorResponse> handleNoSuchElementException(NoSuchElementException e) { | ||||||
| log.warn("RestGlobalExceptionHandler - NoSuchElementException 발생, 찾는 데이터가 없음 {}", e.getMessage(), e); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), e.getMessage()); | ||||||
|
|
||||||
| return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); | ||||||
| } | ||||||
|
|
||||||
| @ExceptionHandler(NullPointerException.class) | ||||||
| public ResponseEntity<ErrorResponse> handleNullPointerException(NullPointerException e) { | ||||||
| log.error("RestGlobalExceptionHandler - NullPointerException 발생 {}", e.getMessage(), e); | ||||||
| ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); | ||||||
|
|
||||||
| return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); | ||||||
| } | ||||||
|
|
||||||
| @ExceptionHandler(IllegalArgumentException.class) | ||||||
| public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) { | ||||||
| log.error("RestGlobalExceptionHandler - IllegalArgumentException 발생, 클라이언트의 잘못된 입력 값 예상 {}", e.getMessage(), e); | ||||||
| ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage()); | ||||||
|
|
||||||
| return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); | ||||||
| } | ||||||
|
|
||||||
| @ExceptionHandler(IndexOutOfBoundsException.class) | ||||||
| public ResponseEntity<ErrorResponse> handleIndexOutOfBoundsException(IndexOutOfBoundsException e) { | ||||||
| log.error("RestGlobalExceptionHandler - IndexOutOfBoundsException 발생, 배열의 범위를 초과한 작업 예상 {}", e.getMessage(), e); | ||||||
| ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage()); | ||||||
|
|
||||||
| return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); | ||||||
| } | ||||||
|
|
||||||
| @ExceptionHandler(DuplicateKeyException.class) | ||||||
| public ResponseEntity<ErrorResponse> handleDuplicateKeyException(DuplicateKeyException e) { | ||||||
| log.error("RestGlobalExceptionHandler - DuplicateKeyException 발생, 유니크/중복 키 충돌 예상 {}", e.getMessage(), e); | ||||||
| ErrorResponse errorResponse = new ErrorResponse(HttpStatus.CONFLICT.value(), e.getMessage()); | ||||||
|
|
||||||
| return new ResponseEntity<>(errorResponse, HttpStatus.CONFLICT); | ||||||
| } | ||||||
|
|
||||||
| @ExceptionHandler(DataAccessException.class) | ||||||
| public ResponseEntity<ErrorResponse> handleDataAccessException(DataAccessException e) { | ||||||
| log.error("RestGlobalExceptionHandler - DataAccessException 발생, 데이터 접근 관련 예외 {}", e.getMessage(), e); | ||||||
| ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); | ||||||
|
|
||||||
| return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); | ||||||
| } | ||||||
|
|
||||||
| @ExceptionHandler(Exception.class) | ||||||
| public ResponseEntity<ErrorResponse> handleException(Exception e) { | ||||||
| log.error("RestGlobalExceptionHandler - Exception 발생, 개발자가 잡지 못한 예외 {}", e.getMessage(), e); | ||||||
| ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); | ||||||
|
|
||||||
| return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); | ||||||
| } | ||||||
|
|
||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Custom Validator 를 적용하면 한방에 처리되지 않을까요?