쇼핑몰 웹사이트 만들어보기 - 리뷰 무한 작성가능 버그 수정

Shiba·2024년 8월 22일
0

프로젝트 및 일기

목록 보기
22/29

이번 글에서는 이전에 만들었던 기능들을 점검해보는 시간을 가져볼까한다.
원래는 검색 후, 정렬기능을 먼저 만들까 했는데 계속 오류들이 눈에 거슬려서 이 작업을 먼저 해두려고한다.

찾은 오류들은 다음과 같다.

  1. 현재 리뷰를 무한정으로 적을 수 있음. 제품을 구매했을 때, 한번만 쓸 수 있도록 고쳐야함
  2. 장바구니 창에서 쿠폰을 적용할 때 하나의 쿠폰을 여러 상품에 중복적용 가능한 버그
  3. 회원 탈퇴 기능이 없음
  4. 주문 내역 탭을 구현하지 않아 기능하지 않음
  5. 주문 취소 기능 구현해야됨
  6. 결제 성공 창을 테스트 결과를 위한 창이 아닌 사용자가 사용할 수 있도록 수정하기

일단은 이정도 있는 것 같다. 하나하나씩 고쳐보도록 하자


  1. 리뷰를 무한정 적을 수 있는 버그

이 버그를 고치기 위해서는 프론트쪽의 수정도 필요하겠지만 백엔드쪽 수정도 필요할 것같다. 지금 리뷰 데이터베이스에는 사용자id와 상품id로 해당 리뷰를 찾아낼 수는 있지만, 이걸로 리뷰를 작성하지 못하게 막아버린다면 사용자가 그 상품을 재구매했을 때, 리뷰를 작성하지 못하게되는 불상사가 발생한다. 따라서 우리는 결제정보, 리뷰 데이터베이스에 결제 시 나오게되는 orderId를 저장하여 이를 사용해서 이 문제를 해결해볼까 한다.

++2024/08/23 00:31) 구현을 완료하고 보니 orderId를 괜히 리뷰 데이터베이스와 공유하려고 하다가 유일성에 위반하여 오류가 떠서 삽질을 좀 했다. 어차피 결제정보가 상품별로 존재하기 때문에 그냥 결제정보를 저장해버리면 되는 문제였다...

그러면 어떻게 구현을 성공했는지 정리해보도록 하자. 먼저 당연히 엔티티를 수정해서 orderId 라는 값을 결제정보가 저장하도록 하고, 리뷰 데이터베이스는 결제정보의 id를 외래키로 가지도록 하자.

결제정보 엔티티에 다음 변수 추가

 @Column(name = "order_id")
    private String order_id;

리뷰 엔티티에서 외래키로 결제정보id를 가지도록 하기

  @OneToOne
    @JoinColumn(name = "purchase_id")
    private Purchases purchases;

그 후, 컨트롤러에서 최근 주문 내역을 볼 수 있는 페이지와 관련된 코드를 수정해주어야 한다

컨트롤러 수정

@GetMapping("/user/status")
    public String profilePage(@AuthenticationPrincipal User user, Model model) {
        Users users = userService.findById(user.getUsername());
        List<Purchases> purchases = purchaseService.getPurchaseWithinOneMonth(users.getId());
        List<Review> reviews = reviewService.findReviewByUserId(users.getId());
        List<PurchaseDto> purchaseDtos= new ArrayList<>();
        for(Purchases p : purchases){
            boolean canReview = true;
            for(Review r : reviews){
                if(p.getId() == r.getPurchases().getId()){
                    canReview = false;
                    break;
                }
            }
            purchaseDtos.add(new PurchaseDto(p,canReview));
        }
        model.addAttribute("user", users);
        model.addAttribute("purchaseDto", purchaseDtos);
        return "/user/status";
    }

컨트롤러에서 결제정보와 리뷰를 둘다 리스트로 저장해서 비교한 후, 이를 Dto로 묶어서 한번에 보내는 방법을 사용했다.

다음으로 결제정보 데이터베이스와 리뷰 데이터베이스가 바뀌었으니 관련된 코드들도 모두 수정을 해야한다.

먼저 결제정보 데이터베이스에 orderId는 토스api 결제 시도 시, 랜덤문자열로 만들도록 코드를 짜놓았기 때문에 이를 그대로 받아와서 저장해주면 된다.

결제정보에 orderId를 저장하도록 하기

// 결제 성공 및 실패 비즈니스 로직을 구현하세요.
        int len = cartIds.length;
        String userId = userDetails.getUsername();
        for(int i = 0; i<len; i++){
            Purchases purchases = new Purchases();
            purchases.setUser_id(userId);
            purchases.setUsers(userService.findById(userId));
            Products p = cartService.findById(cartIds[i]).getProducts();
            purchases.setProduct_id(p.getId());
            purchases.setPurchase_type(paymentType);
            purchases.setProducts(p);
            purchases.setUse_coupon(discounts[i]);
            purchases.setProduct_cnt(quantity[i]);
            purchases.setPrice(price[i]);
            purchases.setOrder_id(orderId);
            purchaseService.addPurchase(purchases);
            cartService.deleteCartItem(cartIds[i]);
        }

이제 리뷰에서 결제정보를 저장할 수 있도록 해주자.

리뷰 생성 코드 수정

public Review writeReview(Users user, Products products, String title, String content, int rating, MultipartFile photo, Purchases purchases){
        String photoPath = "";

        if(photo != null)
            photoPath= fileStorageService.storeFile(photo);

        // 리뷰 저장 로직 구현
        Review review = new Review();
        review.setUsers(user);
        review.setProducts(products);
        review.setContent(content);
        review.setTitle(title);
        review.setRating(rating);
        review.setPurchases(purchases);
        if(photoPath.length() > 0) review.setPhoto(photoPath);

        return reviewRepository.writeReview(review);
    }

리뷰에서 결제정보 id가 필요하므로, 이를 리뷰버튼을 클릭 시, 가져올 수 있도록 하자.

프론트에서 리뷰 버튼 클릭 시, id를 파라미터로 가져오도록 하기

<button class="review" th:if="${item.canReview == true}"
                                th:text="'리뷰 작성'"
                                th:attr="data-order-id=${item.purchases.order_id}, data-product-id=${item.purchases.product_id}"
                                onclick="openReviewPopup(this.getAttribute('data-order-id'),this.getAttribute('data-product-id'))">>
                        </button>
<script>
function openReviewPopup(orderId, productId) {
        const url = '/reviewPopup?orderId=' + encodeURIComponent(orderId) + '&productId=' + encodeURIComponent(productId);
        const windowFeatures = 'width=600,height=400,menubar=no,toolbar=no,location=yes,status=no,resizable=yes,scrollbars=yes';

        window.open(url, 'ReviewPopup', windowFeatures);
    }
</script>

리뷰 POST 명령 컨트롤러 수정

@PostMapping("/submitReview")
    public ResponseEntity<String> submitReview(@RequestParam("order_id") String orderId,
                                               @RequestParam("product_id") int product_id,
                                               @RequestParam("title") String title,
                                               @RequestParam("content") String content,
                                               @RequestParam("rating") int rating,
                                               @RequestParam("photo") MultipartFile photo,
                                               @AuthenticationPrincipal UserDetails userDetails) {
        ResponseEntity response = null;
        try {
            // 리뷰 저장 로직 구현
            Users users = userService.findById(userDetails.getUsername());
            List<Purchases> purchases = purchaseService.findByProductIdAndOrderId(product_id, orderId);
            if(purchases.size() > 0){
                Purchases pur = purchaseService.findByThreeId(users.getId(),product_id,orderId);
                Products p = pur.getProducts();
                reviewService.writeReview(users, p, title, content, rating, photo, pur);
                response = ResponseEntity
                        .status(HttpStatus.CREATED)
                        .body("리뷰가 등록되었습니다!");
            }
            else throw new Exception();
        } catch (Exception ex) {
            response = ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("An exception occured due to " + ex.getMessage());
        }
        return response;
    }

이제 리뷰에 결제정보를 저장해둘 수 있도록 했다. 마지막으로, 리뷰가 이미 존재한다면, '리뷰 작성 완료' 라는 span태그를 보여지게 만들어서 리뷰 작성을 이미 했음을 알리도록 하자.

html 수정

<div class="but_box">
                        <button class="review" th:if="${item.canReview == true}"
                                th:text="'리뷰 작성'"
                                th:attr="data-order-id=${item.purchases.order_id}, data-product-id=${item.purchases.product_id}"
                                onclick="openReviewPopup(this.getAttribute('data-order-id'),this.getAttribute('data-product-id'))">>
                        </button>
                        <span class="review-completed"
                              th:if="${item.canReview == false}"
                              th:text="'리뷰 작성 완료'">
                        </span>
                        <button class="cancel" onclick="cancelOrder()">주문 취소</button>
                    </div>

Dto로 canReview라는 부울 값을 받게되는데 말그대로 리뷰가 작성가능하면 버튼이 보이고, 작성이 불가능하면 밑에 span태그가 보여지는 방식이다.

리뷰를 작성완료하게되면 페이지를 리로드해서 리뷰 작성을 완료했음을 서버에서 받아오도록 하자.

리뷰 js 수정

 window.onunload = function() {
        if (window.opener) {
            window.opener.location.reload();
        }
    };

위 코드를 넣으면 리뷰 창이 종료될 때, 리뷰를 열었던 창이 리로드된다.


테스트 결과


성공적으로 리뷰가 저장됨을 확인했다.


글이 너무 길어졌으니 다음 글에서 다음 버그를 수정해보도록 하자...

profile
모르는 것 정리하기

0개의 댓글