스프링 쇼핑몰 프로젝트 4주차

윤장원·2023년 6월 5일
0

쇼핑몰프로젝트

목록 보기
4/5

상품 주문하기

Ordered, Transaction 엔티티를 생성했다. 주문하기 요청을 받으면 Customer 장바구니 목록을 가져와서 ordered, transaction 테이블에 주문 정보를 저장한다. 그 후 Customer 장바구니 목록 전체를 삭제한다.

Ordered

@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Ordered extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "customer_id")
@ToString.Exclude
private Customer customer;

@ManyToOne
@JoinColumn(name = "product_id")
@ToString.Exclude
private Product product;

@ManyToOne
@JoinColumn(name = "seller_id")
@ToString.Exclude
private Seller seller;

@ManyToOne
@JoinColumn(name = "transaction_id")
@ToString.Exclude
private Transaction transaction;

private Integer price;
private Integer quantity;
private Integer total;
}


> Transaction

```java
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Transaction extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToOne
  @JoinColumn(name = "customer_id")
  @ToString.Exclude
  private Customer customer;

  @OneToMany(mappedBy = "transaction", fetch = FetchType.LAZY)
  @ToString.Exclude
  private List<Ordered> orderedListList = new ArrayList<>();

  private LocalDateTime transactionDate;
  private Integer transactionPrice;
  private Integer pointUse;
  private boolean deletedYn;
}

OrderedController

  @PostMapping
  @PreAuthorize("hasRole('CUSTOMER')")
  public ResponseEntity<String> orderItems(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
      @RequestParam("point") Integer point
  ) {
    orderService.orderItems(token, point);
    return ResponseEntity.ok(ORDER_ITEMS_SUCCESS);
  }

OrderedService

  @Override
  @Transactional
  @CacheEvict(value = "productInfo", allEntries = true)
  public void orderItems(String token, Integer point) {
    Customer customer = getCustomerFromToken(token);

    if (customer.getPoint() < point) {
      throw new CustomException(ErrorCode.NOT_ENOUGH_POINT);
    }

    List<Cart> cartList = cartRepository.findByCustomerId(customer.getId());

    if (cartList.size() == 0) {
      throw new CustomException(ErrorCode.CART_NOT_FOUND);
    }

    List<Ordered> orderedList = new ArrayList<>();
    List<Product> productList = new ArrayList<>();

    int total = 0;

    for (Cart cart : cartList) {
      Product product = cart.getProduct();
      product.setOrderedCnt(product.getOrderedCnt() + 1);
      product.setQuantity(product.getQuantity() - cart.getQuantity());
      Seller seller = cart.getProduct().getSeller();
      int totalPrice = product.getPrice() * cart.getQuantity();

      Ordered ordered = Ordered.builder()
          .customer(customer)
          .product(product)
          .seller(seller)
          .price(product.getPrice())
          .quantity(cart.getQuantity())
          .total(totalPrice)
          .build();

      orderedList.add(ordered);
      productList.add(product);
      total += totalPrice;
    }

    Transaction transaction = Transaction.builder()
        .customer(customer)
        .orderedListList(orderedList)
        .transactionDate(LocalDateTime.now())
        .transactionPrice(total - point)
        .pointUse(point)
        .build();

    for (Ordered ordered : orderedList) {
      ordered.setTransaction(transaction);
    }

    customer.setPoint(customer.getPoint() - point + total / 100);

    productRepository.saveAll(productList);
    customerRepository.save(customer);
    transactionRepository.save(transaction);
    orderedRepository.saveAll(orderedList);
    cartRepository.deleteByCustomerId(customer.getId());
  }

상품 주문이 완료되면 각 상품의 주문 횟수는 1씩 증가하게 되고, 남은 수량은 주문한 만큼 줄어들게 된다.

주문 조회하기

주문했던 상품들 전체 조회, 전체 주문 정보 조회, 한 주문의 상품들 조회 기능을 구현했다. 상품이나 주문 정보를 조회할 때 존재하지 않으면 Custom Error가 발생한다.

OrderedController

@ApiOperation(value = "전체 주문 정보 가져오기")
  @GetMapping
  @PreAuthorize("hasRole('CUSTOMER')")
  public ResponseEntity<List<OrderedItemsForm>> getTotalOrderedItems(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token
  ) {
    return ResponseEntity.ok(orderService.getTotalOrderedItems(token));
  }

  @ApiOperation(value = "한 주문 정보에 대한 모든 ordered 조회하기")
  @GetMapping("/transaction/{id}")
  @PreAuthorize("hasRole('CUSTOMER')")
  public ResponseEntity<List<OrderedItemsForm>> getOrderedItemsByTransaction(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
      @Parameter(name = "id", description = "주문 정보 아이디")
      @PathVariable("id") long id
  ) {
    return ResponseEntity.ok(orderService.getOrderedItemsByTransaction(token, id));
  }

TransactionController

  @ApiOperation(value = "Customer 주문 정보 조회")
  @GetMapping
  @PreAuthorize("hasRole('CUSTOMER')")
  public ResponseEntity<List<TransactionsForm>> getTotalTransactions(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token
  ) {
    return ResponseEntity.ok(orderService.getTotalTransactions(token));
  }

OrderedService

@Override
  public List<OrderedItemsForm> getTotalOrderedItems(String token) {
    Customer customer = getCustomerFromToken(token);
    List<Ordered> orderedList = orderedRepository.findByCustomerId(customer.getId());

    if (orderedList.size() == 0) {
      throw new CustomException(ErrorCode.ORDERED_NOT_FOUND);
    }

    return OrderedItemsForm.of(orderedList);
  }

  @Override
  public List<TransactionsForm> getTotalTransactions(String token) {
    Customer customer = getCustomerFromToken(token);
    List<Transaction> transactionList = transactionRepository.findByCustomerId(customer.getId());

    if (transactionList.size() == 0) {
      throw new CustomException(ErrorCode.TRANSACTION_NOT_FOUND);
    }

    return TransactionsForm.of(transactionList);
  }

  @Override
  public List<OrderedItemsForm> getOrderedItemsByTransaction(String token, Long transactionId) {
    Customer customer = getCustomerFromToken(token);
    List<Ordered> orderedList = orderedRepository.findByCustomerIdAndTransactionId(customer.getId(),
        transactionId);

    if (orderedList.size() == 0) {
      throw new CustomException(ErrorCode.ORDERED_NOT_FOUND);
    }

    return OrderedItemsForm.of(orderedList);
  }

OrderedItemsForm

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderedItemsForm {

  private String name;
  private Integer price;
  private Integer quantity;
  private String image;
  private Long transactionId;
  private LocalDateTime transactionDate;
  private boolean transactionCancelled;

  public static List<OrderedItemsForm> of(List<Ordered> orderedList) {
    return orderedList.stream()
        .map(ordered -> OrderedItemsForm.builder()
            .name(ordered.getProduct().getName())
            .price(ordered.getPrice())
            .quantity(ordered.getQuantity())
            .image(ordered.getProduct().getImage())
            .transactionId(ordered.getTransaction().getId())
            .transactionDate(ordered.getTransaction().getTransactionDate())
            .transactionCancelled(ordered.getTransaction().isDeletedYn())
            .build())
        .collect(Collectors.toList());
  }
}

TransactionsForm

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransactionsForm {

  private Long id;
  private Integer price;
  private Integer pointUse;
  private LocalDateTime transactionDate;
  private boolean transactionCancelled;

  public static List<TransactionsForm> of(List<Transaction> transactionList) {
    return transactionList.stream()
        .map(transaction -> TransactionsForm.builder()
            .id(transaction.getId())
            .price(transaction.getTransactionPrice())
            .pointUse(transaction.getPointUse())
            .transactionDate(transaction.getTransactionDate())
            .transactionCancelled(transaction.isDeletedYn())
            .build())
        .collect(Collectors.toList());
  }
}

리뷰 기능

Customer가 주문한 상품에 대해 리뷰 작성하는 기능, 판매자가 판매하는 상품에 리뷰가 생기면 댓글을 작성하는 기능을 구현했다. 특정 상품에 대한 리뷰들 조회하기 기능 구현해서 리뷰에 달린 판매자의 댓글들도 조회하도록 했다.

Review

@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Review extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToOne
  @JoinColumn(name = "product_id")
  @ToString.Exclude
  private Product product;

  @ManyToOne
  @JoinColumn(name = "customer_id")
  @ToString.Exclude
  private Customer customer;

  @ManyToOne
  @JoinColumn(name = "seller_id")
  @ToString.Exclude
  private Seller seller;

  @OneToOne
  @JoinColumn(name = "ordered_id")
  @ToString.Exclude
  private Ordered ordered;

  @OneToMany(mappedBy = "review", fetch = FetchType.LAZY)
  @ToString.Exclude
  private List<ReviewComment> reviewCommentList = new ArrayList<>();

  private Integer rating;
  private String contents;
  private boolean deletedYn;
}

ReviewController

@RestController
@RequiredArgsConstructor
@RequestMapping("/review")
public class ReviewController {

  private final ReviewService reviewService;

  private static final String REGISTER_REVIEW_SUCCESS = "리뷰 등록 완료";
  private static final String REGISTER_REVIEW_COMMENT_SUCCESS = "리뷰 댓글 등록 완료";

  @ApiOperation(value = "Customer 리뷰 등록")
  @PostMapping
  @PreAuthorize("hasRole('CUSTOMER')")
  public ResponseEntity<String> registerReview(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
      @RequestBody ReviewRegisterForm reviewRegisterForm) {
    reviewService.register(token, reviewRegisterForm.toServiceForm());
    return ResponseEntity.ok(REGISTER_REVIEW_SUCCESS);
  }

  @ApiOperation(value = "리뷰 페이지 가져오기")
  @GetMapping
  public ResponseEntity<List<ReviewDto>> getReviews(
      @Parameter(name = "productId", description = "상품 아이디")
      @RequestParam("productId") Long productId,
      @Parameter(name = "page", description = "페이지")
      @RequestParam("page") Integer page,
      @Parameter(name = "size", description = "페이지 크기")
      @RequestParam("size") Integer size
  ) {
    return ResponseEntity.ok(reviewService.getReviews(productId, page, size));
  }

  @ApiOperation(value = "리뷰 답변 등록")
  @PostMapping("/comment")
  @PreAuthorize("hasRole('SELLER')")
  public ResponseEntity<String> registerReviewComment(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
      @RequestBody ReviewCommentRegisterForm reviewCommentRegisterForm) {
    reviewService.registerReviewComment(token, reviewCommentRegisterForm.toServiceForm());
    return ResponseEntity.ok(REGISTER_REVIEW_COMMENT_SUCCESS);
  }
}

ReviewService

  @Override
  public void register(String token, ReviewRegisterServiceForm form) {
    Customer customer = getCustomerFromToken(token);

    if (!checkReviewRegisterForm(form)) {
      throw new CustomException(ErrorCode.INVALID_REVIEW_FORM);
    }

    Ordered ordered = orderedRepository.findById(form.getOrderId())
        .orElseThrow(() -> new CustomException(ErrorCode.ORDERED_NOT_FOUND));

    Optional<Review> optionalReview = reviewRepository.findByOrderedId(ordered.getId());
    if (optionalReview.isPresent()) {
      throw new CustomException(ErrorCode.REVIEW_ALREADY_EXIST);
    }

    if (!Objects.equals(ordered.getCustomer().getId(), customer.getId())) {
      throw new CustomException(ErrorCode.CUSTOMER_NOT_MATCH);
    }

    Product product = ordered.getProduct();
    Seller seller = product.getSeller();

    Review review = Review.builder()
        .product(product)
        .customer(customer)
        .seller(seller)
        .ordered(ordered)
        .rating(form.getRating())
        .contents(form.getContents())
        .deletedYn(false)
        .build();

    reviewRepository.save(review);
  }

  @Override
  public List<ReviewDto> getReviews(Long productId, Integer page, Integer size) {
    Pageable pageable = PageRequest.of(page - 1, size);

    List<Review> reviewList = reviewRepository.findByProductId(productId, pageable);

    if (reviewList.size() == 0) {
      throw new CustomException(ErrorCode.REVIEW_NOT_FOUND);
    }

    return ReviewDto.toDtoList(reviewList);
  }

  @Override
  public void registerReviewComment(String token, ReviewCommentRegisterServiceForm form) {
    if (isStringEmpty(form.getContents())) {
      throw new CustomException(ErrorCode.INVALID_REVIEW_FORM);
    }

    Seller seller = getSellerFromToken(token);
    Review review = reviewRepository.findById(form.getReviewId())
        .orElseThrow(() -> new CustomException(ErrorCode.REVIEW_NOT_FOUND));

    if (!Objects.equals(seller.getId(), review.getSeller().getId())) {
      throw new CustomException(ErrorCode.SELLER_NOT_MATCH);
    }

    ReviewComment reviewComment = ReviewComment.builder()
        .review(review)
        .contents(form.getContents())
        .deletedYn(false)
        .build();

    reviewCommentRepository.save(reviewComment);
  }

최근 본 상품과 관련 인기 상품 불러오기

상품 상세 정보를 조회하면 해당 IP가 조회한 상품을 데이터베이스에 저장한다. 최근 본 상품과 관련 인기 상품 불러오기를 요청하면 데이터베이스에서 상품을 조회하고 해당 카테고리에서 상품 주문 횟수가 많은 순으로 4개를 더 가져온다.

먼저 요청을 받으면 IP주소를 얻어오는 클래스를 구현했다.

ClientUtils

public class ClientUtils {

  public static String getIp(HttpServletRequest request) {
    String ip = request.getHeader("X-Forwarded-For");

    if (ip == null) {
      ip = request.getHeader("Proxy-Client-IP");
    }
    if (ip == null) {
      ip = request.getHeader("WL-Proxy-Client-IP");
    }
    if (ip == null) {
      ip = request.getHeader("HTTP_CLIENT_IP");
    }
    if (ip == null) {
      ip = request.getHeader("HTTP_X_FORWARDED_FOR");
    }
    if (ip == null) {
      ip = request.getRemoteAddr();
    }

    return ip;
  }
}

그 후 상품 상세 정보 불러오기 기능에 IP를 받으면 조회한 상품 정보를 저장하는 기능을 추가했다.

ProductViewHistory

@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class ProductViewHistory extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @OneToOne
  @JoinColumn(name = "product_id")
  @ToString.Exclude
  private Product product;

  private String userIp;
}

ProductController

@ApiOperation(value = "한 상품의 상세정보 조회")
  @GetMapping("/info/{id}")
  public ResponseEntity<ProductInfo> getProductInfo(
      @PathVariable long id,
      HttpServletRequest request
  ) {
    String userIp = ClientUtils.getIp(request);
    return ResponseEntity.ok(productService.getProductInfo(id, userIp));
  }

  @ApiOperation(value = "접속한 Ip에서 조회한 상품과 관련된 인기 상품 조회")
  @GetMapping("/recent")
  public ResponseEntity<List<ProductInfo>> getRecentViewedProducts(
      HttpServletRequest request,
      @Parameter(name = "page", description = "페이지")
      @RequestParam("page") Integer page,
      @Parameter(name = "size", description = "페이지 크기")
      @RequestParam("size") Integer size
  ) {
    String userIp = ClientUtils.getIp(request);
    return ResponseEntity.ok(productService.getRecentViewedProducts(userIp, page, size));
  }

ProductService

@Override
  @Cacheable(key = "#id", value = "productInfo")
  public ProductInfo getProductInfo(Long id, String userIp) {
    Product product = productRepository.findById(id)
        .orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));

    Optional<ProductViewHistory> optionalProductViewHistory = productViewHistoryRepository.findByUserIp(
        userIp);

    ProductViewHistory productViewHistory;

    if (optionalProductViewHistory.isPresent()) {
      productViewHistory = optionalProductViewHistory.get();
      productViewHistory.setProduct(product);
    } else {
      productViewHistory = ProductViewHistory.builder()
          .product(product)
          .userIp(userIp)
          .build();

    }
    productViewHistoryRepository.save(productViewHistory);

    return ProductInfo.from(product);
  }

  @Override
  public List<ProductInfo> getRecentViewedProducts(String userIp, Integer page, Integer size) {
    Optional<ProductViewHistory> optionalProductViewHistory = productViewHistoryRepository.findByUserIp(
        userIp);

    if (optionalProductViewHistory.isEmpty()) {
      throw new CustomException(ErrorCode.PRODUCT_NOT_FOUND);
    }

    Product product = optionalProductViewHistory.get().getProduct();
    Long categoryId = product.getCategory().getId();

    Pageable pageable = PageRequest.of(page - 1, size, Sort.by("orderedCnt").descending());

    Page<Product> productList = productRepository.findAllByCategoryId(categoryId, pageable);

    List<ProductInfo> productInfoList = ProductInfo.toList(productList);

    productInfoList.add(0, ProductInfo.from(product));

    return productInfoList;
  }

상품에 대해 문의하기

Customer는 상품에 대한 문의를 등록, Seller는 자신이 판매하는 상품에 문의가 달리면 답변을 할 수 있는 기능을 구현했다. 상품에 대한 문의를 조회하면 문의와 문의에 대한 답변 목록들이 조회된다.

Question

@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Question extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToOne
  @JoinColumn(name = "product_id")
  @ToString.Exclude
  private Product product;

  @ManyToOne
  @JoinColumn(name = "customer_id")
  @ToString.Exclude
  private Customer customer;

  @ManyToOne
  @JoinColumn(name = "seller_id")
  @ToString.Exclude
  private Seller seller;

  @OneToMany(mappedBy = "question", fetch = FetchType.LAZY)
  @ToString.Exclude
  private List<Answer> answerList = new ArrayList<>();

  private String contents;
  private boolean deletedYn;
}

Answer

@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Answer extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToOne
  @JoinColumn(name = "question_id")
  @ToString.Exclude
  private Question question;

  @ManyToOne
  @JoinColumn(name = "seller_id")
  @ToString.Exclude
  private Seller seller;

  private String contents;
  private boolean deletedYn;
}

QuestionController

@RestController
@RequiredArgsConstructor
@RequestMapping("/question")
public class QuestionController {

  private final QuestionService questionService;

  private static final String REGISTER_QUESTION_SUCCESS = "문의 등록 완료";
  private static final String REGISTER_ANSWER_SUCCESS = "답변 등록 완료";

  @ApiOperation(value = "Customer 상품 문의 등록")
  @PostMapping
  @PreAuthorize("hasRole('CUSTOMER')")
  public ResponseEntity<String> registerQuestion(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
      @RequestBody QuestionRegisterForm form) {
    questionService.registerQuestion(token, form.toServiceForm());
    return ResponseEntity.ok(REGISTER_QUESTION_SUCCESS);
  }

  @ApiOperation(value = "Seller 문의 답변 등록")
  @PostMapping("/answer")
  @PreAuthorize("hasRole('SELLER')")
  public ResponseEntity<String> registerAnswer(
      @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
      @RequestBody AnswerRegisterForm form) {
    questionService.registerAnswer(token, form.toServiceForm());
    return ResponseEntity.ok(REGISTER_ANSWER_SUCCESS);
  }

  @ApiOperation(value = "상품에 대한 문의 조회")
  @GetMapping
  public ResponseEntity<List<QuestionDto>> getQuestions(
      @Parameter(name = "productId", description = "상품 아이디")
      @RequestParam("productId") Long productId,
      @Parameter(name = "page", description = "페이지")
      @RequestParam("page") Integer page,
      @Parameter(name = "size", description = "페이지 크기")
      @RequestParam("size") Integer size
  ) {
    return ResponseEntity.ok(questionService.getQuestions(productId, page, size));
  }
}

QuestionService

  @Override
  public void registerQuestion(String token, QuestionRegisterServiceForm form) {
    Customer customer = getCustomerFromToken(token);

    Product product = productRepository.findById(form.getProductId())
        .orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));

    Seller seller = product.getSeller();

    Question question = Question.builder()
        .product(product)
        .customer(customer)
        .seller(seller)
        .contents(form.getContents())
        .deletedYn(false)
        .build();

    questionRepository.save(question);
  }

  @Override
  public void registerAnswer(String token, AnswerRegisterServiceForm form) {
    Seller seller = getSellerFromToken(token);

    Question question = questionRepository.findById(form.getQuestionId())
        .orElseThrow(() -> new CustomException(ErrorCode.QUESTION_NOT_FOUND));

    Answer answer = Answer.builder()
        .question(question)
        .seller(seller)
        .contents(form.getContents())
        .deletedYn(false)
        .build();

    answerRepository.save(answer);
  }

  @Override
  public List<QuestionDto> getQuestions(Long productId, Integer page, Integer size) {
    Pageable pageable = PageRequest.of(page - 1, size);

    List<Question> questionList = questionRepository.findByProductId(productId, pageable);

    if (questionList.size() == 0) {
      throw new CustomException(ErrorCode.QUESTION_NOT_FOUND);
    }

    return QuestionDto.toDtoList(questionList);
  }

0개의 댓글