-
Notifications
You must be signed in to change notification settings - Fork 118
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
부산대 BE_문성민 2주차 과제 (3단계) #402
base: dalsungmin
Are you sure you want to change the base?
Changes from all commits
b395b28
89c8f2f
8a69b0d
cfdccd2
0141677
a253ad3
d7cb8b0
6e5a64e
d509f6e
6bd1a0e
6b027df
3e11707
766f93b
3611e8a
3a15901
c058262
18554db
686f8e3
1285b16
3b40b3f
ab9c207
a108ea6
36b7a5d
4951fbe
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 |
---|---|---|
@@ -1 +1,56 @@ | ||
# spring-gift-wishlist | ||
# spring-gift-wishlist | ||
# Step 0 | ||
* 상품 관리 코드를 옮겨 온다. 코드를 옮기는 방법에는 디렉터리의 모든 파일을 직접 복사하여 붙여 넣는 것부터 필요한 일부 파일만 이동하는 것, Git을 사용하는 것까지 여러 가지 방법이 있다. 코드 이동 시 반드시 리소스 파일, 프로퍼티 파일, 테스트 코드 등을 함께 이동한다. | ||
*** | ||
# Step 1유효성 검사 및 예외처리 | ||
- 상품을 추가하거나 수정하는 경우, 클라이언트로부터 잘못된 값이 전달될 수 있다. | ||
- 잘못된 값이 전달되면 클라이언트가 어떤 부분이 왜 잘못되었는지 인지할 수 있도록 응답을 제공한다. | ||
## 예외 조건 | ||
* 상품 이름은 공백을 포함하여 최대 15자까지 입력할 수 있다. | ||
* 특수 문자 | ||
* 가능: ( ), [ ], +, -, &, /, _ | ||
* 그 외 특수 문자 사용 불가 | ||
* "카카오"가 포함된 문구는 담당 MD와 협의한 경우에만 사용할 수 있다. | ||
*** | ||
# Step 2 인증 | ||
* 사용자가 회원 가입, 로그인, 추후 회원별 기능을 이용할 수 있도록 구현한다. | ||
* 회원은 이메일과 비밀번호를 입력하여 가입한다. | ||
* 토큰을 받으려면 이메일과 비밀번호를 보내야 하며, 가입한 이메일과 비밀번호가 일치하면 토큰이 발급된다. | ||
* 토큰을 생성하는 방법에는 여러 가지가 있다. 방법 중 하나를 선택한다. | ||
* (선택) 회원을 조회, 추가, 수정, 삭제할 수 있는 관리자 화면을 구현한다. | ||
* | ||
* 아래 예시와 같이 HTTP 메시지를 주고받도록 구현한다. | ||
### Reaquest | ||
POST /login/token HTTP/1.1 \ | ||
content-type: application/json\ | ||
host: localhost:8080 | ||
|
||
{\ | ||
"password": "password",\ | ||
"email": "[email protected]"\ | ||
} | ||
### Response | ||
HTTP/1.1 200\ | ||
Content-Type: application/json | ||
|
||
{\ | ||
" token": ""\ | ||
} | ||
|
||
### 로그인 | ||
POST /members/login HTTP/1.1 | ||
content-type: application/json | ||
host: localhost:8080 | ||
|
||
{\ | ||
"email": "[email protected]", | ||
"password": "password"\ | ||
} | ||
|
||
### Response | ||
HTTP/1.1 200 | ||
Content-Type: application/json | ||
|
||
{\ | ||
"token": ""\ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package gift.controller; | ||
|
||
|
||
import gift.model.LoginResponse; | ||
import gift.model.Member; | ||
import gift.service.MemberService; | ||
import gift.util.JwtUtil; | ||
import java.util.Map; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
@Controller | ||
@RequestMapping("/members") | ||
public class MemberController { | ||
private final MemberService memberService; | ||
private final JwtUtil jwtUtil; | ||
|
||
public MemberController(MemberService memberService) { | ||
this.memberService = memberService; | ||
this.jwtUtil = new JwtUtil(); | ||
} | ||
|
||
@PostMapping("/register") | ||
public ResponseEntity<?> register(@RequestBody Member member) { | ||
if (memberService.existsByEmail(member.getEmail())) { | ||
return ResponseEntity.status(HttpStatus.CONFLICT).body("Email already exists"); | ||
} | ||
Member registeredMember = memberService.registerMember(member); | ||
String token = jwtUtil.generateToken(registeredMember.getId(), registeredMember.getName(), | ||
registeredMember.getRole()); | ||
return ResponseEntity.ok(Map.of("token", token)); | ||
} | ||
|
||
@PostMapping("/login") | ||
public ResponseEntity<?> login(@RequestBody Member loginRequest) { | ||
Member member = memberService.authenticate(loginRequest.getEmail(), | ||
loginRequest.getPassword()); | ||
if (member != null) { | ||
String token = jwtUtil.generateToken(member.getId(), member.getName(), | ||
member.getRole()); | ||
return ResponseEntity.ok(new LoginResponse(token)); | ||
} else { | ||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid email or password"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package gift.controller; | ||
|
||
import gift.exception.InvalidProductException; | ||
import gift.exception.ProductNotFoundException; | ||
import gift.model.Product; | ||
import gift.service.ProductService; | ||
import jakarta.validation.Valid; | ||
import java.util.List; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.*; | ||
import org.springframework.web.servlet.mvc.support.RedirectAttributes; | ||
|
||
@Controller | ||
@RequestMapping("/products") | ||
public class ProductController { | ||
private final ProductService productService; | ||
|
||
public ProductController( ProductService productService) { | ||
this.productService = productService; | ||
} | ||
|
||
@GetMapping | ||
public ResponseEntity<List<Product>> getAllProducts(@RequestHeader("Authorization") String token) { | ||
List<Product> products = productService.getAllProducts(); | ||
return ResponseEntity.ok(products); | ||
} | ||
|
||
@PostMapping | ||
public String addProduct(@ModelAttribute @Valid Product product, RedirectAttributes redirectAttributes) { | ||
try { | ||
productService.addProduct(product); | ||
} catch (InvalidProductException e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Invalid product: " + e.getMessage()); | ||
} | ||
|
||
return "redirect:/products"; | ||
} | ||
|
||
@PostMapping("/{id}") | ||
public String updateProduct(@Valid @PathVariable Long id, @ModelAttribute Product product, RedirectAttributes redirectAttributes) { | ||
try { | ||
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. 여기서 try catch로 작업을 다 해주셨는데 이렇게되면
이렇게 구성하고 컨트롤러 단에서 try catch 걷어 냅시다!
모든지 새로운 기술을 습득하거나 적용할때 왜 왜 왜 ! 항상 머리속에 왜라는 생각을 꼭 하셔서 @ControllerAdvice 를 왜 사용할까? |
||
productService.updateProduct(id, product); | ||
} catch (InvalidProductException e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Invalid product: " + e.getMessage()); | ||
} catch (ProductNotFoundException e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Product not found: " + e.getMessage()); | ||
} catch (Exception e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Error updating product: " + e.getMessage()); | ||
} | ||
return "redirect:/products"; | ||
} | ||
|
||
@PostMapping("/delete/{id}") | ||
public String deleteProduct(@PathVariable Long id, RedirectAttributes redirectAttributes) { | ||
try { | ||
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. 위와 같이 수정해주세요. |
||
productService.deleteProduct(id); | ||
} catch (ProductNotFoundException e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Product not found: " + e.getMessage()); | ||
} catch (Exception e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Error deleting product: " + e.getMessage()); | ||
} | ||
return "redirect:/products"; | ||
} | ||
|
||
@GetMapping("/view/{id}") | ||
public String getProductDetails(@PathVariable("id") Long id, Model model, RedirectAttributes redirectAttributes) { | ||
try { | ||
Product product = productService.getProductById(id); | ||
model.addAttribute("product", product); | ||
} catch (ProductNotFoundException e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Product not found: " + e.getMessage()); | ||
} catch (Exception e) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Product not found" ); | ||
return "redirect:/products"; | ||
} | ||
return "product-detail"; | ||
} | ||
|
||
@GetMapping("/{id}") | ||
@ResponseBody | ||
public Product getProductById(@PathVariable("id") Long id) { | ||
try { | ||
return productService.getProductById(id); | ||
} catch (Exception e) { | ||
throw new IllegalArgumentException("Product not found: " + e.getMessage()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package gift.controller; | ||
|
||
import gift.model.WishList; | ||
import gift.service.WishListService; | ||
import gift.util.JwtUtil; | ||
import io.jsonwebtoken.Claims; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.List; | ||
|
||
@RestController | ||
@RequestMapping("/wishlist") | ||
public class WishListController { | ||
private final WishListService wishListService; | ||
private final JwtUtil jwtUtil; | ||
|
||
public WishListController(WishListService wishListService, JwtUtil jwtUtil) { | ||
this.wishListService = wishListService; | ||
this.jwtUtil = jwtUtil; | ||
} | ||
|
||
@GetMapping | ||
public ResponseEntity<?> getWishList(@RequestHeader("Authorization") String token) { | ||
Long memberId = jwtUtil.extractMemberId(token); | ||
List<WishList> wishList = wishListService.getWishListByMemberId(memberId); | ||
return ResponseEntity.ok(wishList); | ||
} | ||
|
||
@PostMapping | ||
public ResponseEntity<?> addProductToWishList(@RequestHeader("Authorization") String token, @RequestParam Long productId) { | ||
Long memberId = jwtUtil.extractMemberId(token); | ||
WishList wishList = wishListService.addProductToWishList(memberId, productId); | ||
return ResponseEntity.ok(wishList); | ||
} | ||
|
||
@DeleteMapping("/{id}") | ||
public ResponseEntity<Void> removeProductFromWishList(@RequestHeader("Authorization") String token, @PathVariable Long id) { | ||
wishListService.removeProductFromWishList(id); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package gift.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
||
@ResponseStatus(HttpStatus.FORBIDDEN) | ||
public class ForbiddenException extends RuntimeException { | ||
public ForbiddenException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package gift.exception; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.ControllerAdvice; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.servlet.mvc.support.RedirectAttributes; | ||
@ControllerAdvice | ||
public class GlobalExceptionHandler { | ||
|
||
@ExceptionHandler(Exception.class) | ||
public String handleException(Exception e, RedirectAttributes redirectAttributes) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Error: " + e.getMessage()); | ||
return "redirect:/products"; | ||
} | ||
|
||
@ExceptionHandler(ProductNotFoundException.class) | ||
public String handleProductNotFoundException(ProductNotFoundException e, RedirectAttributes redirectAttributes) { | ||
redirectAttributes.addFlashAttribute("errorMessage", "Product not found: " + e.getMessage()); | ||
return "redirect:/products"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package gift.exception; | ||
|
||
public class InvalidProductException extends RuntimeException { | ||
public InvalidProductException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package gift.exception; | ||
public class ProductNotFoundException extends RuntimeException { | ||
public ProductNotFoundException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package gift.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
||
@ResponseStatus(HttpStatus.UNAUTHORIZED) | ||
public class UnauthorizedException extends RuntimeException { | ||
public UnauthorizedException(String message) { | ||
super(message); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package gift.model; | ||
|
||
public class LoginResponse { | ||
private String token; | ||
|
||
public LoginResponse(String token) { | ||
this.token = token; | ||
} | ||
|
||
public String getToken() { | ||
return token; | ||
} | ||
|
||
public void setToken(String token) { | ||
this.token = token; | ||
} | ||
} |
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.
jwt의 원리와. 단점 파악하기.
위 처럼 jwt를 사용하는 정확한 이유과 장단점을 확인하면서 학습하기를 권장 드립니다 :)