✔️ 주요 코드 중심 분석 ✔️
리뷰 상세 보기
Front
useEffect(() => {
let getDetailURL = `${url}/review/${reviewNo}`; // 1
if (isLogin) { getDetailURL += `?memNo=${memNo}`; }
axios.get(getDetailURL)
.then((res) => {
setReview(res.data.review); // 2
setLike(res.data.isLike); // 3
setWish(res.data.isWish); // 4
setLikeCount(res.data.review.likeCount); // 5
})
.catch((error) => {
console.error("에러:" + error);
});
}, [reviewNo]);
- review/{reviewNo} 엔드포인트로 리뷰 번호와 (회원 번호)를 담아 GET 요청 전달
- 응답 데이터로 받은 리뷰 정보 저장
- 응답 데이터로 받은 추천 여부 저장
- 응답 데이터로 받은 찜 여부 저장
- 응답 데이터로 받은 추천수 저장
관련 이슈 1리뷰 상세 정보를 가져올 때,
로그인 여부에 따라 찜 / 추천 여부를 함께 가져와야 함
(찜 / 추 현재 상태 표시를 위해)
해결 방법
let getDetailURL = `${url}/review/${reviewNo}`; if (isLogin) { getDetailURL += `?memNo=${memNo}`; }로그인 하지 않은 방문자의 경우 기본 리뷰 상세 정보만 요청
로그인 한 회원의 경우 해당 회원 번호를 파라미터로 전달 해 해당 리뷰에 대한 찜 / 추 여부를 boolean 값으로 받음
관련 이슈 2리뷰 상세 정보를 요청하는 페이지가 많았음
1. 사용자 마이페이지 - 찜한 리뷰 리스트, 리뷰 관리, 댓글 관리
2. 사장님 마이페이지 - 특정 카페에 작성된 리뷰 리스트
3. 지도 - 특정 카페에 작성된 리뷰 리스트
4. 리뷰 목록
특히, 찜한 리뷰 리스트에서 요청하는 경우 모달로 띄움
해결 방법
const ReviewDetail = ({ modalDetail, wishReviewNo }) => { const location = useLocation(); const listReviewNo = location.state?.reviewNo; const reviewNo = wishReviewNo || listReviewNo; // 생략useLocation() hook을 사용하여 현재 페이지의 상태에서 reviewNo 값을 가져옴
만약 상태가 존재하지 않는 경우 props 값을 선택하고, 존재하는 경우 해당 값을 선택하도록 함
이때, 옵셔널 체이닝 연산자(?)를 사용해 속성이 없는 경우 발생하는 TypeError를 방지navigate('/reviewDetail/' + reviewNo, { state: { reviewNo: reviewNo } }); // 또는 <Link to={`/reviewDetail/${review.reviewNo}`} state={{ reviewNo: `${review.reviewNo}` }} >1. 리뷰 상세 정보를 요청하며 페이지 이동을 처리하는 경우, useNavigate()에 상태를 담아 전달
<ReviewDetail modalDetail wishReviewNo = {wishReviewNo}/>2. 찜한 리뷰 리스트에서 모달로 띄우려고 하는 경우, props를 전달
Back
@GetMapping("/review/{reviewNo}")
public ResponseEntity<Object> getReviewDetail(@PathVariable Integer reviewNo,
@RequestParam(required = false) Integer memNo) { // 1
try {
Map<String, Object> res = new HashMap<>();
ReviewDetailDto review = reviewService.reviewDetail(reviewNo);
res.put("review", review);
boolean isLike = reviewService.isLikeReview(memNo, reviewNo);
res.put("isLike", isLike);
boolean isWish = reviewService.isWishReview(memNo, reviewNo);
res.put("isWish", isWish);
Integer modDate = review.getRegDate()
.plusDays(3).getDayOfYear() - LocalDateTime.now().getDayOfYear(); // 2
res.put("modDate", modDate);
return new ResponseEntity<>(res, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
public ReviewDetailDto reviewDetail(Integer reviewNo) throws Exception {
return detailRepository.findReviewByReviewNo(reviewNo);
}
public boolean isLikeReview(Integer memNo, Integer reviewNo) throws Exception {
return likeRepository.existsByMember_memNoAndReview_reviewNo(memNo, reviewNo); // 3
}
public boolean isWishReview(Integer memNo, Integer reviewNo) throws Exception {
return wishRepository.existsByMember_memNoAndReview_reviewNo(memNo, reviewNo); // 3
}
- 클라이언트에서 전달 받은 데이터를 통해 서비스 레이어로 해당 회원 정보 요청
- 우리 서비스는 리뷰 수정이 작성일로부터 3일간만 가능하기 때문에, 수정 가능 일자와 찜 / 추 정보를 함께 저장
- 데이터베이스에서 해당 회원이 리뷰에 찜 / 추천한 데이터를 찾아 반환
리뷰 추천하기
Front
const toggleLike = () => {
if (isLogin) { // 1
axios.post(`${url}/member/like/${memNo}/${reviewNo}`, null, // 2
{
headers: {
Authorization: accessToken,
Refresh: getCookie("refreshToken")
}
})
.then((res) => {
tokenCreate(dispatch, setCookie, res.headers)
.then(() => {
setLike(res.data.toggleLike); // 3
setLikeCount(res.data.likeCount); // 4
})
})
// 이하 생략
- 로그인 한 회원만 사용할 수 있는 기능이므로 로그인 여부 먼저 체크
- member/like/{memNo}/{reviewNo} 엔드포인트로 POST 요청 전달
- 응답 데이터를 통해 리뷰 추천 / 취소
- 응답 데이터를 통해 리뷰 추천 개수 카운트 (선택 / 취소 즉시 추천 수 변화)
Back
@PostMapping("member/like/{memNo}/{reviewNo}")
public ResponseEntity<Object> isLikeReview(@PathVariable Integer memNo,
@PathVariable Integer reviewNo) {
try {
Map<String, Object> res = new HashMap<>();
boolean toggleLike = reviewService.toggleLikeReview(memNo, reviewNo); // 1
res.put("toggleLike", toggleLike); // 2
Integer likeCount = reviewService.reviewDetail(reviewNo).getLikeCount(); // 1
res.put("likeCount", likeCount); // 2
return new ResponseEntity<>(res, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
@Transactional // 3
public boolean toggleLikeReview(Integer memNo, Integer reviewNo) throws Exception {
Review review = reviewRepository.findByReviewNo(reviewNo);
Member member = memberRepository.findByMemNo(memNo);
boolean isLike = likeRepository.existsByMember_memNoAndReview_reviewNo(memNo, reviewNo);
if (isLike) {
likeRepository.deleteByMember_memNoAndReview_reviewNo(memNo, reviewNo);
review.setLikeCount(review.getLikeCount() - 1); // 추천 수 감소
return false; // 추천 취소
} else {
likeRepository.save(LikeReview.builder().member(member).review(review).build());
review.setLikeCount(review.getLikeCount() + 1); // 추천 수 증가
return true; // 추천
}
}
- 클라이언트에서 전달 받은 데이터를 통해 서비스 레이어로 해당 정보 요청
- 추천 선택 / 취소와 추천수를 담아 결과 반환
- 추천 선택 / 취소와 추천수를 업데이트 하는 작업을 하나의 트랜잭션으로 처리하여 데이터 일관성 유지
리뷰 찜하기
추천수 카운트하는 부분을 제외하면, 리뷰 추천과 동일한 로직을 가지고 있음
'[Project] CafeHub' 카테고리의 다른 글
| [CafeHub] 코드 분석 및 이슈 기록 - 회원 정보 수정 / 찜한 리뷰 리스트 / 찜한 카페 리스트 (0) | 2024.01.08 |
|---|---|
| [CafeHub] 코드 분석 및 이슈 기록 - 카페 정보 (with. 카카오 지도) (0) | 2024.01.08 |
| [CafeHub] 코드 분석 및 이슈 기록 - 아이디 찾기 / 비밀번호 찾기 (0) | 2024.01.05 |
| [CafeHub] 기획 - 담당 역할에 따른 화면 정의서 (0) | 2023.12.23 |
| [CafeHub] 기획 - 담당 역할에 따른 사용자 요구 사항 정의서 (0) | 2023.12.23 |

