-
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 13 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,68 @@ | ||||||
| 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.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/customer") | ||||||
| @RequiredArgsConstructor | ||||||
| public class CustomerApiController { | ||||||
|
|
||||||
| private final CustomerService customerService; | ||||||
|
|
||||||
| @PostMapping("/save") | ||||||
| public ResponseEntity<CustomerResponse> save(@Validated CustomerCreateRequest customerCreateRequest) { | ||||||
| CustomerValidator.validateCustomer( | ||||||
| customerCreateRequest.getCustomerName(), | ||||||
| customerCreateRequest.getCustomerEmail(), | ||||||
| customerCreateRequest.getCustomerType() | ||||||
| ); | ||||||
|
||||||
|
|
||||||
| CustomerResponse customerResponse = customerService.save(customerCreateRequest); | ||||||
|
|
||||||
| return new ResponseEntity<>(customerResponse, HttpStatus.CREATED); | ||||||
| } | ||||||
|
|
||||||
| @GetMapping("/find") | ||||||
|
||||||
| public ResponseEntity<List<CustomerResponse>> getFindAll() { | ||||||
| CustomerListResponse customerListResponse = customerService.findAll(); | ||||||
|
|
||||||
| return ResponseEntity.ok(customerListResponse.getCustomerList()); | ||||||
| } | ||||||
|
|
||||||
| @GetMapping("/find/{id}") | ||||||
|
||||||
| public ResponseEntity<CustomerResponse> findById(@PathVariable("id") UUID customerId) { | ||||||
| CustomerResponse customerResponse = customerService.findById(customerId); | ||||||
|
|
||||||
| return ResponseEntity.ok(customerResponse); | ||||||
| } | ||||||
|
|
||||||
| @GetMapping("/delete/{id}") | ||||||
|
||||||
| @GetMapping("/delete/{id}") | |
| @DeleteMapping("/{id}") |
http method 종류에 대해 공부해봅시다!
https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%A2%85%EB%A5%98-%ED%86%B5%EC%8B%A0-%EA%B3%BC%EC%A0%95-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC
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.
예전에 흘려듣기로 Delete와 Put메소드를 사용하면 보안에 좋지 않다라는 말을 들었었는데 찾아보니 제가 잘 안찾아보고 그냥 안썼던거네요ㅠ http method 종류를 직접 적용할 수 있도록 해보겠습니다!!
delete method 적용 커밋 : fa951d0
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.
관련된 내용은 요거같네요
https://velog.io/@awdsza/HTTPMethod-PUTDELETE%EB%B3%B4%EC%95%88-%EC%9C%84%ED%97%98%EC%84%B1
http method의 멱등성에 대해서도 공부해보시길 바랍니다~
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||
| 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.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/voucher") | ||||||
| @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("/save") | ||||||
| public ResponseEntity<VoucherResponse> save(@Validated VoucherCreateRequest voucherCreateRequest) { | ||||||
| VoucherValidator.validateVoucher( | ||||||
| voucherCreateRequest.getVoucherType(), | ||||||
| String.valueOf(voucherCreateRequest.getDiscountAmount()) | ||||||
| ); | ||||||
|
|
||||||
| VoucherResponse voucherResponse = voucherService.save(voucherCreateRequest); | ||||||
|
|
||||||
| return new ResponseEntity<>(voucherResponse, HttpStatus.CREATED); | ||||||
|
||||||
| return new ResponseEntity<>(voucherResponse, HttpStatus.CREATED); | |
| return ResponseEntity.status(HttpStatus.CREATED).body(voucherResponse); |
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.
| 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 |
|---|---|---|
|
|
@@ -6,12 +6,11 @@ | |
| import org.springframework.dao.DuplicateKeyException; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.web.bind.annotation.ControllerAdvice; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| 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); | ||||||
| } | ||||||
|
|
||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.
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.
rest api url 규칙을 읽어보시고, 반드시 지켜주세요!
구글링해서 아무거나 ... https://velog.io/@pjh612/REST-API-URI-%EA%B7%9C%EC%B9%99
아래 리뷰부터는 꼭 공부하시고 읽어주세요~
리소스는 복수형으로 써주세요
Uh oh!
There was an error while loading. Please reload this page.
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.
생각보다 정말 많은 규칙이 있는데 너무 마음대로 사용했던 것 같습니다..!
확실히 rest api uri 규칙을 지키면 간결하고 가독성도 좋은 것 같아요ㅎㅎ 말씀하신대로 다 적용해서 고쳐보도록 하겠습니다!
복수형으로 변경한 커밋 : fa951d0