Skip to content
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_안원모_3주차 과제(0,1,2단계) #251

Merged
merged 41 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8753eee
STEP0를 위한 기존 코드 가져오기
Wonmoan Jul 11, 2024
0eb427a
Refactor : MebemerController의 else 로직 리펙토링
Wonmoan Jul 11, 2024
5f26e8f
Refactor : RuntimeException 응답방식 수정
Wonmoan Jul 11, 2024
1b6f069
Refactor : JwtService 키 생성방식 최신화
Wonmoan Jul 11, 2024
eb87ec1
Refactor : 사용 하지 않는 주석 제거
Wonmoan Jul 11, 2024
16400e6
Refactor : 응답 반환 형식 통일
Wonmoan Jul 11, 2024
e4514c0
Refactor : WishController 오류 처리 GlobalExceptionHandler에서 수행하도록 변경
Wonmoan Jul 11, 2024
e620367
Refactor : layerd architecture 따른 패키지 구조 조정
Wonmoan Jul 11, 2024
625a5c4
Refactor : JPA 리팩토링에 따른 변경사항 추가
Wonmoan Jul 11, 2024
b8a2981
Refactor : wish 테이블 포함한 schema로 수정
Wonmoan Jul 11, 2024
39c50eb
Refactor : Product 도메인 JPA로 리펙토링 엔티티 클래스로 변경
Wonmoan Jul 11, 2024
2b7e729
Refactor : Member 도메인 JPA로 리펙토링 엔티티 클래스로 변경
Wonmoan Jul 11, 2024
f991f62
Refactor : 패키지 경로 수정
Wonmoan Jul 11, 2024
f7aec38
Refactor : JPA 저장소로 리팩토링
Wonmoan Jul 11, 2024
6cd3537
Feat : @DataJpaTest 활용 회원저장소 테스트 코드 구현
Wonmoan Jul 11, 2024
4389367
Feat : @DataJpaTest 활용 회원저장소 테스트 코드 구현
Wonmoan Jul 11, 2024
b1c90bb
Feat : @DataJpaTest 활용 상품저장소 테스트 코드 구현
Wonmoan Jul 11, 2024
0831c87
Feat : @DataJpaTest 활용 위리리스트 테스트 코드 구현
Wonmoan Jul 11, 2024
d9ce007
Reafactor : JPA형식에 따른 update 메서드 수정
Wonmoan Jul 11, 2024
448076c
Refactor : JWT 서명 키 수정
Wonmoan Jul 11, 2024
f576700
Refactor : 코드 컨벤션에 맞게 수정
Wonmoan Jul 11, 2024
008ad15
Docs : README.md 작성
Wonmoan Jul 11, 2024
75edcc2
Refactor : Member,Product,Wish 도메인 객체 참조 & 외래 키 매핑
Wonmoan Jul 12, 2024
56ba257
Docs : README.md작성
Wonmoan Jul 12, 2024
923e2e3
Refactor : DB 초기데이터 동적 생성,삽입
Wonmoan Jul 12, 2024
b5fd6ef
Refactor : 불필요한 어노테이션 삭제
Wonmoan Jul 13, 2024
ab9dc63
Refactor : getValidMember 메서드 예외 처리방식 변경
Wonmoan Jul 13, 2024
e897194
Refactor : DataLoader 사용 하지 않는 throw 제거
Wonmoan Jul 13, 2024
2282ccc
Refactor : 로깅 포매팅 방식으로 변경
Wonmoan Jul 13, 2024
cacfaae
Refactor : 불필요한 어노테이션 삭제
Wonmoan Jul 13, 2024
2e67bfd
Refacotr : 불필요한 throws Exception 제거
Wonmoan Jul 13, 2024
6d0f590
Refactor : id 값 중복으로 받지 않도록 수정
Wonmoan Jul 13, 2024
ad48400
Refactor : 기존 controller 에서 예외 처리 방식 resolver로 예외처리 방식으로 변경
Wonmoan Jul 13, 2024
98c2893
Refactor : JwtService 사용하지 않는 메서드 삭제
Wonmoan Jul 13, 2024
c5d7da7
Refactor : 코드 가독성 수정
Wonmoan Jul 13, 2024
b1184c2
Refactor : Member 도메인 기능 분리, MemberDTO생성
Wonmoan Jul 13, 2024
d7982bd
Refactor : Product 도메인 기능 분리 , ProductDTO 생성
Wonmoan Jul 13, 2024
e3cdc64
Refactor : 컨트롤러 DTO 받도록 수정
Wonmoan Jul 13, 2024
0b5f982
Refacotr : 불필요한 어노테이션 삭제
Wonmoan Jul 13, 2024
6608fcf
Refactor : JSON 직렬화 수정
Wonmoan Jul 13, 2024
4a1ea83
Refactor : 디버깅 목적 로깅 삭제
Wonmoan Jul 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
# spring-gift-jpa
# spring-gift-jpa
### step0
- 위시리스트 작업물 세팅

### step1
- 필요 의존성 추가 및 properties 추가
- 각 entity JPA로 변환
- Repository class 생성 및 구현
- @DataJpaTest를 이용한 Test생성 및 구현

## step2
- 객체의 참조와 테이블의 외래 키를 매핑해서 객체에서는 참조를 사용
- 테이블에서는 외래 키를 사용
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ dependencies {
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

tasks.named('test') {
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/gift/config/DataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gift.config;

import gift.domain.Product;
import gift.repository.ProductRepository;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DataLoader implements CommandLineRunner {

private final ProductRepository productRepository;

@Autowired
public DataLoader(ProductRepository productRepository) {
this.productRepository = productRepository;
}

@Override
public void run(String... args) throws Exception {
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 3; i <= 10; i++) {
Product product = new Product.ProductBuilder()
.name("Product" + i)
.price(BigDecimal.valueOf(10.00 * i))
.imageUrl("http://example.com/product" + i)
.description("Description for Product" + i)
.build();
productRepository.save(product);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data.sql 과 해당 방식을 함께 사용하게 된 이유가 궁금해요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 .. 다음 step인 step3 에서 페이지네이션 작성하여 확인할 때 편의성을 위해 작성하였습니다.
data.sql에는 현재 product2 까지 2개의 상품으로 초기 데이터가 작성 되어 있는데,
이 경우, 상품 여러개를 추가하여 페이지네이션 작동을 확인 할 때 data.sql파일을 업데이트를 계속해서 해줘야 할 것 같아 해당 방식을 사용하였습니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나의 방식으로 통일하는게 좋을 것 같네요.

Copy link
Author

@Wonmoan Wonmoan Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음.. 위시 리스트 구현방법에 대해 질문 드리고 다음 step에 반영 하겠습니다.

 @Override
    public void run(String... args) throws Exception {
        for (int i = 3; i <= 10; i++) {
            Product product = new Product.ProductBuilder()
                .name("Product" + i)
                .price(BigDecimal.valueOf(10.00 * i))
                .imageUrl("http://example.com/product" + i)
                .description("Description for Product" + i)
                .build();
            productRepository.save(product);
        }
    }

현재 구현된 방법은 상품에 대한 구체적인 정보가 단순히 인덱스만 증가되어 추가되는 형태입니다.
하지만 실제 상품의 경우는 이름도 다 다르고 imageurl, description같은 경우에도 인덱싱뿐만 아니라 설명하는 내용이 다를텐데 이런 요구사항을 어떻게 반영해야 할까요?

기존의 방법으로 data.sql에 상품의 구성요소들을 등록하는 방법으로 통일하는 편이 좋을까요?
아니면, JSON, CSV와 같은 상품 정보를 담은 외부 파일에서 데이터를 읽어와서 초기화 하는 방법으로 통일하는 편이 좋을가요?

추후에 위시 리스트가 추가적으로 어떻게 구현 될진 모르겠지만 어떤 방법이 앞으로의 작업을 진행하는데 있어서 더 나은 방향일까 고민이 됩니다.

Copy link
Author

@Wonmoan Wonmoan Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4주차 step1과제를 좀 진행 해보면서
위와 같은 초기데이터 설정은 페이지네이션 작동이 잘 되는지 확인할 때
여러 상품이 잘 등록,조회가 될수 있나? 테스트할 때 사용되기 좋은 방식이지 실제로 초기 데이터를 등록 할 때는 좋은 방법이 아닌 것 같다라고 생각이 들었습니다.

제가 이해한 4주차 step1은 상품등록에 있어 등록 할 수 있는 카테고리의 범주가 한정 되어있다고 생각했습니다.
(교환권, 상품권, 뷰티, 패션, 식품, 리빙/도서, 레저/스포츠, 아티스트/캐릭터, 유아동/반려, 디지털/가전, 카카오프렌즈, 트렌드 선물, 백화점)
저 로직은 여러 상품을 만들어 잘 작동 되는 지 확인하는 용으로 사용하고
앞으로 진행할 프로젝트에서는 data.sql 방식을 사용할까 합니다.

}
21 changes: 21 additions & 0 deletions src/main/java/gift/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gift.config;

import gift.util.LoginMemberArgumentResolver;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginMemberArgumentResolver loginMemberArgumentResolver;

public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) {
this.loginMemberArgumentResolver = loginMemberArgumentResolver;
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberArgumentResolver);
}
}
75 changes: 75 additions & 0 deletions src/main/java/gift/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package gift.controller;

import gift.service.JwtService;
import gift.util.LoginMember;
import gift.domain.Member;
import gift.service.MemberService;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/members")
public class MemberController {

private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
private final MemberService memberService;
private final JwtService jwtService;

public MemberController(MemberService memberService, JwtService jwtService) {
this.memberService = memberService;
this.jwtService = jwtService;
}

@PostMapping("/register")
public ResponseEntity<Map<String, String>> register(@Valid @RequestBody Member member) {
try {
Member savedMember = memberService.createMember(member);
Map<String, String> response = new HashMap<>();
response.put("token", jwtService.generateToken(savedMember));
logger.debug("Register - Generated Token: " + response.get("token"));
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved
return ResponseEntity.ok(response);
} catch (Exception e) {
throw new RuntimeException("Error during registration: " + e.getMessage(), e);
}
Comment on lines +39 to +41

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외를 잡아서 다시 runtime exception 으로 바꿔주시는 이유가 궁금합니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음.. exception을 잡아서 다시 RuntimeException으로 예외를 잡아서 다시 던지면서
예외 메시지를 추가하거나 로깅을 통해 디버깅 정보를 더 자세하게 나타내보려 했습니다.
변경하는 편이 코드 가독성에 더 도움이 된다면 변경하겠습니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 방식을 적용하는 경우도 있긴하지만, 지금은 말해주신

  • 의미있는 예외 메시지를 추가
  • 로깅

둘 다 하지 않는 상황인 것 같아요. 가독성도 그렇지만, 불필요한 것 같다는 생각이 가장 크게 들어서
저는 변경하면 좋을 것 같네요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PostMapping("/register")
    public ResponseEntity<Map<String, String>> register(@Valid @RequestBody MemberDTO memberDTO) {
        Member member = memberDTO.toEntity();
        Member savedMember = memberService.createMember(member);
        Map<String, String> response = new HashMap<>();
        response.put("token", jwtService.generateToken(savedMember));
        return ResponseEntity.ok(response);
    }

GlobalExceptionHandler 에서 에외처리 할 수 있도록
기존의 예외를 다시 던지는 코드 수정했습니다.

}

@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@Valid @RequestBody Member member) {
Member foundMember = getValidMember(member);

Map<String, String> response = new HashMap<>();
response.put("token", jwtService.generateToken(foundMember));
logger.debug("Login - Generated Token: " + response.get("token"));
return ResponseEntity.ok(response);
}

private Member getValidMember(Member member) {
Optional<Member> foundMemberOpt = memberService.getMemberByEmail(member.getEmail());
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved
if (foundMemberOpt.isEmpty() || !isPasswordValid(foundMemberOpt.get(), member.getPassword())) {
throw new IllegalArgumentException("Invalid email or password");
}
return foundMemberOpt.get();
}

private boolean isPasswordValid(Member foundMember, String password) {
return foundMember.getPassword().equals(password);
}
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved

@GetMapping("/profile")
public ResponseEntity<Member> getProfile(@LoginMember Member member) {
if (member == null) {
throw new IllegalArgumentException("Member not found or unauthorized");
}
return ResponseEntity.ok(member);
}
}
44 changes: 44 additions & 0 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gift.controller;

import gift.domain.Product;
import gift.service.ProductService;
import jakarta.validation.Valid;
import java.util.List;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

@GetMapping
public List<Product> getProducts() {
return productService.findAllProducts();
}

@PostMapping
public Product postProduct(@Valid @RequestBody Product product) {
return productService.createProduct(product);
}

@PutMapping("/{id}")
public Product putProduct(@PathVariable Long id, @Valid @RequestBody Product product) {
return productService.updateProduct(id, product);
}

@DeleteMapping("/{id}")
public Long deleteProduct(@PathVariable Long id) {
return productService.deleteProduct(id);
}
}
70 changes: 70 additions & 0 deletions src/main/java/gift/controller/ProductWebController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package gift.controller;

import gift.domain.Product;
import gift.service.ProductService;
import java.math.BigDecimal;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.RequestParam;

@Controller
@RequestMapping("/web/products")
public class ProductWebController {
private final ProductService productService;

public ProductWebController(ProductService productService) {
this.productService = productService;
}

@GetMapping
public String getProductsPage(Model model) {
List<Product> products = productService.findAllProducts();
model.addAttribute("products", products);
return "products";
}

@PostMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-8")
public String postProduct(@RequestParam String name, @RequestParam BigDecimal price,
@RequestParam String imageUrl, @RequestParam String description) {
Product product = new Product.ProductBuilder()
.name(name)
.price(price)
.imageUrl(imageUrl)
.description(description)
.build();
productService.createProduct(product);
return "redirect:/web/products";
}

@PostMapping(value = "/delete", consumes = "application/x-www-form-urlencoded;charset=UTF-8")
public String deleteProduct(@RequestParam List<Long> productIds) {
for (Long id : productIds) {
productService.deleteProduct(id);
}
return "redirect:/web/products";
}

@GetMapping("/edit/{id}")
public String getEditForm(@PathVariable Long id, Model model) {
Product product = productService.getProductById(id);
model.addAttribute("product", product);
return "productEdit";
}

@PostMapping(value = "/edit/{id}", consumes = "application/x-www-form-urlencoded;charset=UTF-8")
public String editProduct(@RequestParam Long id, @RequestParam String name, @RequestParam BigDecimal price, @RequestParam String imageUrl) {
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved
Product product = new Product.ProductBuilder()
.id(id)
.name(name)
.price(price)
.imageUrl(imageUrl)
.build();
productService.updateProduct(id, product);
return "redirect:/web/products";
}
}
53 changes: 53 additions & 0 deletions src/main/java/gift/controller/WishController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package gift.controller;

import gift.util.LoginMember;
import gift.domain.Member;
import gift.domain.Wish;
import gift.dto.WishRequest;
import gift.service.WishService;
import jakarta.validation.Valid;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/wishes")
public class WishController {
private final WishService wishService;

public WishController(WishService wishService) {
this.wishService = wishService;
}

@GetMapping
public ResponseEntity<List<Wish>> getWishes(@LoginMember Member member) {
if (member == null) {
throw new IllegalArgumentException("Member not found or unauthorized");
}
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved
List<Wish> wishes = wishService.getWishesByMemberId(member.getId());
return ResponseEntity.ok(wishes);
}
Wonmoan marked this conversation as resolved.
Show resolved Hide resolved

@PostMapping
public ResponseEntity<String> addWish(@RequestBody @Valid WishRequest request, @LoginMember Member member) {
if (member == null) {
throw new IllegalArgumentException("Member not found or unauthorized");
}
wishService.addWish(member.getId(), request.getProductId());
return ResponseEntity.ok("Wish added successfully");
}

@DeleteMapping
public ResponseEntity<String> removeWish(@RequestBody @Valid WishRequest request, @LoginMember Member member) {
if (member == null) {
throw new IllegalArgumentException("Member not found or unauthorized");
}
wishService.removeWish(member.getId(), request.getProductId());
return ResponseEntity.ok("Wish removed successfully");
}
}
Loading