Self-Study/계란으로 바위치기
[Spring] 상품 찜하기 기능
Raadian
2021. 4. 16. 17:47
1. 요구사항
- 상품 상세페이지에 로그인한 회원만 찜을 할 수 있어야 한다.
- : 비 로그인시 로그인 화면으로 페이지 이동하도록 조치
- 권한 등급이 'MEMBER'(회원)만 찜을 할 수 있어야 한다.
- : 그 외의 권한 등급의 경우 '찜불가'라는 화면이 뜨도록 조치
- 찜을 하면 마이페이지에 있는 찜한 상품 리스트 항목으로 페이지 이동할건지 알림 창이 뜨도록 한다.
- 찜이 되면 해당 상품이 찜이 되었다는 UI가 뜰 수 있도록 한다.
- 찜한 상태에서 찜하기 버튼을 다시 누르면 다시 초기화가 될 수 있도록 한다.
2. 구현사항
2-1. Controller
// 상품 상세페이지
@GetMapping("/prdct/{prdct_id}")
public ModelAndView productDetail(@PathVariable("prdct_id") String p_id, @AuthenticationPrincipal MemberDetails memberDetails,
PrdReviewCriteria rcri, PrdQnACriteria qacri, PrdctLikeVO prdctLikeVO, ModelAndView mav) throws Exception {
... 중략
// 해당 상품 찜 여부 확인용 데이터 가져오기
log.info("prdLike...");
mav.addObject("prdLikeVal", commonService.getPrdLikeVal(p_id));
return mav;
}
// 상품 상세페이지 찜하기 기능
@Transactional(rollbackFor = Exception.class)
@PostMapping("/prdct/{prdct_id}")
public ResponseEntity<String> prdctLike(@RequestBody PrdctLikeVO prdctLikeVO) {
ResponseEntity<String> entity = null;
log.info("prdctLike...");
try {
commonService.setPrdctLike(prdctLikeVO);
entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
return entity;
}
// 상품 상세페이지 찜취소 기능
@Transactional(rollbackFor = Exception.class)
@DeleteMapping("/prdct/{prdct_id}")
public ResponseEntity<String> prdctLikeCancel(PrdctLikeVO prdctLikeVO) {
ResponseEntity<String> entity = null;
log.info("prdctLikeCancel...");
try {
commonService.prdctLikeCancel(prdctLikeVO);
entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
return entity;
}
2-2. Serivce
// 해당 상품 찜 여부 확인용 데이터 가져오기
public PrdctLikeVO getPrdLikeVal(String prdct_id);
// 상품 상세 페이지 찜하기
public void setPrdctLike(PrdctLikeVO prdctLikeVO);
// 상품 상세페이지 찜취소 기능
public int prdctLikeCancel(PrdctLikeVO prdctLikeVO);
// 해당 상품 찜 여부 확인용 데이터 가져오기
@Override
public PrdctLikeVO getPrdLikeVal(String prdct_id) {
log.info("getPrdLikeVal...");
return commonMapper.getPrdLikeVal(prdct_id);
}
// 상품 상세 페이지 찜하기
@Override
public void setPrdctLike(PrdctLikeVO prdctLikeVO) {
log.info("setPrdctLike...");
commonMapper.setPrdctLike(prdctLikeVO);
}
// 상품 상세페이지 찜취소 기능
@Override
public int prdctLikeCancel(PrdctLikeVO prdctLikeVO) {
log.info("prdctLikeCancel...");
return commonMapper.prdctLikeCancel(prdctLikeVO);
}
2-3. Mapper
// 해당 상품 찜 여부 확인용 데이터 가져오기
public PrdctLikeVO getPrdLikeVal(String prdct_id);
// 상품 상세 페이지 찜하기
public void setPrdctLike(PrdctLikeVO prdctLikeVO);
// 상품 상세페이지 찜취소 기능
public int prdctLikeCancel(PrdctLikeVO prdctLikeVO);
<!-- 해당 상품 찜 여부 확인용 데이터 가져오기 -->
<select id="getPrdLikeVal" resultType="edu.bit.ex.vo.PrdctLikeVO">
<![CDATA[
SELECT * FROM prdct_like WHERE prdct_id = #{prdct_id}
]]>
</select>
<!-- 상품 상세 페이지 찜하기 -->
<insert id="setPrdctLike">
<![CDATA[
INSERT INTO prdct_like (prdct_like_number, mbr_id, prdct_id, board_id)
VALUES (prdct_like_seq.nextval, #{mbr_id}, #{prdct_id}, #{board_id})
]]>
</insert>
<!-- 상품 상세페이지 찜취소 기능 -->
<delete id="prdctLikeCancel">
<![CDATA[
DELETE FROM prdct_like WHERE prdct_like_number = #{prdct_like_number}
]]>
</delete>
2-4. View(JSP)
<!-- 찜하기 버튼을 누를경우 이벤트 발생 -->
<script type="text/javascript">
$(document).ready(function() {
$('#prdct_like_dis').click(function(event) {
event.preventDefault();
// 비로그인 상태시 찜하기 버튼을 누르면
if ("${mbr.mbr_id}" == "") {
if (confirm("로그인 한 회원만 이용가능합니다. 로그인 하시겠습니까?")) {
// 승낙하면 로그인 페이지로 이동
location.href = '${pageContext.request.contextPath}/login';
} else {
// 거부하면 해당 페이지 새로고침
location.reload();
}
// 로그인 상태시 찜하기 버튼을 누르면
} else {
// 해당 멤버ID와 상품ID의 정보를 가져온다
var mbr_id = "${mbr.mbr_id}";
var prdct_id = "${prdct.prdct_id}";
var board_id = ${prdct.board_id};
console.log("mbr_id: " + mbr_id);
console.log("mbr_id type: " + (typeof mbr_id));
console.log("prdct_id: " + prdct_id);
console.log("prdct_id type: " + (typeof prdct_id));
console.log("board_id: " + board_id);
console.log("board_id type: " + (typeof board_id));
var form = {
mbr_id : mbr_id,
prdct_id : prdct_id,
board_id : board_id
};
$.ajax({
type : "POST",
url : "${pageContext.request.contextPath}/prdct/{prdct_id}",
cache : false,
contentType : 'application/json; charset=utf-8',
data : JSON.stringify(form),
success : function(result) {
console.log(result);
if (result == "SUCCESS") {
console.log("찜 성공!")
if (confirm("해당 상품을 찜하셨습니다. 목록 페이지로 이동하시겠습니까?")) {
// 승낙하면 마이페이지의 찜하기 리스트로 이동
location.href = '${pageContext.request.contextPath}/member/mypage/like';
} else {
// 거부하면 해당 페이지 새로고침하여 찜한거 반영되게하기(HTTP의 속성 때문...)
location.reload();
}
}
},
error : function(e) {
console.log(e);
alert('찜할 수 없습니다.');
location.reload(); // 실패시 새로고침하기
}
})
}
});
});
</script>
<!-- 찜취소 버튼을 누를경우 이벤트 발생 -->
<script type="text/javascript">
$(document).ready(function() {
$('#prdct_like_ena').click(function(event) {
event.preventDefault();
// FormData 객체 생성
var formData = new FormData();
// 정보를 가져와 FormData에 append 한다
var prdct_like_number = $('#prd_like_num').text();
console.log("prdct_like_number: " + prdct_like_number);
console.log("prdct_like_number: " + (typeof prdct_like_number));
formData.append("prdct_like_number", prdct_like_number);
$.ajax({
type : 'DELETE',
url : $(this).attr("href"),
cache : false,
processData: false,
contentType: false,
data: formData,
success: function(result) {
console.log(result);
if (result == "SUCCESS") {
console.log("찜 취소 성공!")
alert('해당 상품을 찜 취소 하셨습니다.');
location.href = '${pageContext.request.contextPath}/prdct/${prdct.prdct_id}';
}
},
error : function(e) {
console.log(e);
alert('찜 취소 할 수 없습니다.');
location.reload(); // 실패시 새로고침하기
}
})
});
});
</script>
... 중략
<!-- 상품 찜 기능 -->
<div class="icon" style="float: left; padding-left: 20px; padding-top: 10px;">
<!-- 찜하기 했을 경우 해당 정보 가져오기 -->
<span id="prd_like_num" style="display: none;">${prdLikeVal.prdct_like_number}</span>
<%-- 찜하기 기능은 고객(MEMBER 권한)만 이용할 수 있게 설정 --%>
<sec:authorize access="isAnonymous()">
<%-- 로그인 상태가 아니므로 자동으로 로그인 comfirm창이 뜨게 설정 --%>
<i id="prdct_like_dis" class="fa fa-heart-o fa-2x" onclick="location.href='${pageContext.request.contextPath}/prdct/{prdct_id}'">찜하기</i>
</sec:authorize>
<sec:authorize access="hasAuthority('ADMIN')">
<i class="fa fa-heart-o fa-2x">찜불가</i>
</sec:authorize>
<sec:authorize access="hasAuthority('SELLER')">
<i class="fa fa-heart-o fa-2x">찜불가</i>
</sec:authorize>
<sec:authorize access="hasAuthority('MEMBER')">
<c:choose>
<%-- prdct_like 테이블을 가져와 비교후 예전에 찜하기를 했었다면 찜취소로 활성화가 된다 --%>
<c:when test="${(prdLikeVal.prdct_id eq prdct.prdct_id) and (prdLikeVal.mbr_id eq mbr.mbr_id)}">
<i id="prdct_like_ena" class="fa fa-heart fa-2x" onclick="location.href='${pageContext.request.contextPath}/prdct/{prdct_id}'">찜취소</i>
</c:when>
<%-- prdct_like 테이블을 가져와 비교후 예전에 찜하기를 안했다면(혹은 찜취소를 했었다면) 찜하기로 활성화가 된다 --%>
<c:otherwise>
<i id="prdct_like_dis" class="fa fa-heart-o fa-2x" onclick="location.href='${pageContext.request.contextPath}/prdct/{prdct_id}'">찜하기</i>
</c:otherwise>
</c:choose>
</sec:authorize>
</div>
3. 구현 도중 느낀점
- REST를 이용한 Spring 개발도중 Mapping을 어떤 상황에서 적재적소로 활용해야 하는지 그리고 URI가 중복되지 않도록 한다.
- Mybatis에서 '#{}'를 이용하여 값을 유동적으로 넘길 때 NULL 값을 넘기진 않았는지 확인한다.
- AJAX로 값을 넘길때 해당 값이 올바르게 되어있는지 console 로그로 값과 타입을 반드시 확인한다.
- DB내 테이블을 새로 추가해야 되는 사항이 있을 때 불필요하게 많은 컬럼들을 설정하지 않는다.