관리 메뉴

거니의 velog

(3) 도서 쇼핑몰 만들기 3 본문

Java_Spring mini project 쇼핑몰

(3) 도서 쇼핑몰 만들기 3

Unlimited00 2023. 11. 24. 08:43

4. 상품 상세 구현하기

* 이번에는 상품 상세 페이지와 상품 조회 기능을 구현해 보자.

1. 매퍼 파일인 goods.xml에 상품 상세 페이지에 표시한 상품 정보와 상품 메인 이미지 및 상세 이미지 정보를 조회하는 SQL문을 추가한다.

	<!-- 상품 정보와 상품 메인 이미지 파일 정보를 조회한다. -->
	<select id="selectGoodsDetail" resultMap="goodsResult"   parameterType="String"  >
	    <![CDATA[
		     select g.*,d.fileName from t_shopping_goods g, t_goods_detail_image d
	        where g.goods_id=d.goods_id
	        and d.filetype='main_image'
	        and g.goods_id=#{goods_id}
	        order by g.goods_id
    	]]>
	</select>
	
	<!-- 상품 상세 이미지 파일 정보를 조회한다. -->
	<select id="selectGoodsDetailImage" resultMap="imageResult"   parameterType="String"  >
	    <![CDATA[
		    	select * from  t_goods_detail_image 
		    	where  fileType!='main_image'
				and goods_id=#{goods_id}
    	]]>
	</select>

2. 상품 관련 기능을 구현한 자바 클래스를 준비한다.

* 상품 상세 이미지를 표시하면서 빠른 메뉴(퀵 메뉴라고도 함)에 최근 본 상품을 추가하여 표시한다. 빠른 메뉴에는 최대 네 개 까지의 상품을 저장할 수 있다.

* 다음은 빠른 메뉴에 최근 본 상품을 추가하고 표시하는 과정이다.

(1) 세션에 저장된 최근 본 상품을 추가하고 표시하는 과정이다.

(2) 상품 목록에 저장된 상품 개수가 네 개 미만이고 방금 본 상품이 상품 목록에 있는지 체크한다.

(3) 없으면 상품 목록에 추가한다.

(4) 다시 상품 목록을 세션에 추가한다.

(5) 화면에 상품을 표시하는 quickMenu.jsp에서는 세션의 최근 본 상품 목록을 가져와 차례대로 표시한다.

3. 이 과정을 GoodsControllerImpl 클래스에 다음과 같이 구현한다.

package com.bookshop01.goods.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.bookshop01.common.base.BaseController;
import com.bookshop01.goods.service.GoodsService;
import com.bookshop01.goods.vo.GoodsVO;

import net.sf.json.JSONObject;

@Controller("goodsController")
@RequestMapping(value = "/goods")
public class GoodsControllerImpl extends BaseController implements GoodsController {
	@Autowired
	private GoodsService goodsService;

	@RequestMapping(value = "/goodsDetail.do", method = RequestMethod.GET)
	public ModelAndView goodsDetail(@RequestParam("goods_id") String goods_id, // 조회할 상품 번호를 전달받는다.
			HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		String viewName = (String) request.getAttribute("viewName");
		HttpSession session = request.getSession();
		Map goodsMap = goodsService.goodsDetail(goods_id); // 상품 정보를 조회한 후 Map으로 반환한다.
		ModelAndView mav = new ModelAndView(viewName);
		mav.addObject("goodsMap", goodsMap);
		GoodsVO goodsVO = (GoodsVO) goodsMap.get("goodsVO");
		addGoodsInQuick(goods_id, goodsVO, session); // 조회한 상품 정보를 빠른 메뉴에 표시하기 위해 전달한다.
		return mav;
	}

	...

	private void addGoodsInQuick(String goods_id, GoodsVO goodsVO, HttpSession session) {
		
		boolean already_existed = false;
		List<GoodsVO> quickGoodsList; // 최근 본 상품 저장 ArrayList
		quickGoodsList = (ArrayList<GoodsVO>) session.getAttribute("quickGoodsList"); // 세션에 저장된 최근 본 상품 목록을 가져온다.

		if (quickGoodsList != null) { // 최근 본 상품이 있는 경우
			if (quickGoodsList.size() < 4) { // 미리본 상품 리스트에 상품개수가 네 개 이하인 경우
				for (int i = 0; i < quickGoodsList.size(); i++) {
					GoodsVO _goodsBean = (GoodsVO) quickGoodsList.get(i);
					if (goods_id.equals(_goodsBean.getGoods_id())) {
						already_existed = true;
						break;
					}
				} // 상품 목록을 가져와 이미 존재하는 상품인지 비교한다.
				if (already_existed == false) {
					quickGoodsList.add(goodsVO);
				} // already_existed가 false이면 상품 정보를 목록에 저장한다.
			}

		} else {
			quickGoodsList = new ArrayList<GoodsVO>();
			quickGoodsList.add(goodsVO);

		}
		session.setAttribute("quickGoodsList", quickGoodsList); // 최근 본 상품 목록을 세션에 저장한다.
		session.setAttribute("quickGoodsListNum", quickGoodsList.size()); // 최근 본 상품 목록에 저장된 상품 개수를 세션에 저장한다.
		
	}
	
}

4. GoodsServiceImpl 클래스를 다음과 같이 작성한다.

package com.bookshop01.goods.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.bookshop01.goods.dao.GoodsDAO;
import com.bookshop01.goods.vo.GoodsVO;
import com.bookshop01.goods.vo.ImageFileVO;

@Service("goodsService")
@Transactional(propagation = Propagation.REQUIRED)
public class GoodsServiceImpl implements GoodsService {
	
	@Autowired
	private GoodsDAO goodsDAO;

	...

	public Map goodsDetail(String _goods_id) throws Exception {
		Map goodsMap = new HashMap();
		GoodsVO goodsVO = goodsDAO.selectGoodsDetail(_goods_id);
		goodsMap.put("goodsVO", goodsVO);
		List<ImageFileVO> imageList = goodsDAO.selectGoodsDetailImage(_goods_id);
		goodsMap.put("imageList", imageList); // 상품 정보와 이미지 정보를 조회한 후 HashMap에 저장한다.
		return goodsMap;
	}

5. 상품 상세를 화면에 표시할 JSP 파일을 준비한다.

6. goodsDetail.jsp에서는 상품 상세 페이지를 보여주는 페이지로 테이블에서 조회한 상품 정보를 표시한다. 상품 목차 등에 개행문자(\r\n)가 포함되어 있으면 웹 페이지에서는 이를 <br /> 태그로 사용해 바꿔줘야 한다. 즉, 테이블의 상품 목차를 웹 페이지에 표시하면서 개행 기능을 유지하려면 개행문자 (\r\n) 를 <br/> 태그로 대체해서 표시해야 상품 목차를 등록할 때의 형태로 웹 페이지에 표시된다.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8" 	isELIgnored="false"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<c:set var="goods" value="${goodsMap.goodsVO}" />
<c:set var="imageList" value="${goodsMap.imageList }" />
<%
     //치환 변수 선언합니다.
      pageContext.setAttribute("crcn", "\r\n"); //개행문자
      pageContext.setAttribute("crcn" , "\n"); //Ajax로 변경 시 개행 문자 
      pageContext.setAttribute("br", "<br/>"); //br 태그
%>
<html>

<head>
    <style>
        #layer {
            z-index: 2;
            position: absolute;
            top: 0px;
            left: 0px;
            width: 100%;
        }

        #popup {
            z-index: 3;
            position: fixed;
            text-align: center;
            left: 50%;
            top: 45%;
            width: 300px;
            height: 200px;
            background-color: #ccffff;
            border: 3px solid #87cb42;
        }

        #close {
            z-index: 4;
            float: right;
        }
    </style>
    <script type="text/javascript">
        function add_cart(goods_id) {
            $.ajax({
                type: "post",
                async: false, //false인 경우 동기식으로 처리한다.
                url: "${contextPath}/cart/addGoodsInCart.do",
                data: {
                    goods_id: goods_id

                },
                success: function(data, textStatus) {
                    //alert(data);
                    //	$('#message').append(data);
                    if (data.trim() == 'add_success') {
                        imagePopup('open', '.layer01');
                    } else if (data.trim() == 'already_existed') {
                        alert("이미 카트에 등록된 상품입니다.");
                    }

                },
                error: function(data, textStatus) {
                    alert("에러가 발생했습니다." + data);
                },
                complete: function(data, textStatus) {
                    //alert("작업을완료 했습니다");
                }
            }); //end ajax	
        }

        function imagePopup(type) {
            if (type == 'open') {
                // 팝업창을 연다.
                jQuery('#layer').attr('style', 'visibility:visible');

                // 페이지를 가리기위한 레이어 영역의 높이를 페이지 전체의 높이와 같게 한다.
                jQuery('#layer').height(jQuery(document).height());
            } else if (type == 'close') {

                // 팝업창을 닫는다.
                jQuery('#layer').attr('style', 'visibility:hidden');
            }
        }

        function fn_order_each_goods(goods_id, goods_title, goods_sales_price, fileName) {
            var _isLogOn = document.getElementById("isLogOn");
            var isLogOn = _isLogOn.value;

            if (isLogOn == "false" || isLogOn == '') {
                alert("로그인 후 주문이 가능합니다!!!");
            }


            var total_price, final_total_price;
            var order_goods_qty = document.getElementById("order_goods_qty");

            var formObj = document.createElement("form");
            var i_goods_id = document.createElement("input");
            var i_goods_title = document.createElement("input");
            var i_goods_sales_price = document.createElement("input");
            var i_fileName = document.createElement("input");
            var i_order_goods_qty = document.createElement("input");

            i_goods_id.name = "goods_id";
            i_goods_title.name = "goods_title";
            i_goods_sales_price.name = "goods_sales_price";
            i_fileName.name = "goods_fileName";
            i_order_goods_qty.name = "order_goods_qty";

            i_goods_id.value = goods_id;
            i_order_goods_qty.value = order_goods_qty.value;
            i_goods_title.value = goods_title;
            i_goods_sales_price.value = goods_sales_price;
            i_fileName.value = fileName;

            formObj.appendChild(i_goods_id);
            formObj.appendChild(i_goods_title);
            formObj.appendChild(i_goods_sales_price);
            formObj.appendChild(i_fileName);
            formObj.appendChild(i_order_goods_qty);

            document.body.appendChild(formObj);
            formObj.method = "post";
            formObj.action = "${contextPath}/order/orderEachGoods.do";
            formObj.submit();
        }
    </script>
</head>

<body>
    <hgroup>
        <h1>컴퓨터와 인터넷</h1>
        <h2>국내외 도서 &gt; 컴퓨터와 인터넷 &gt; 웹 개발</h2>
        <h3>${goods.goods_title }</h3>
        <h4>${goods.goods_writer} &nbsp; 저| ${goods.goods_publisher}</h4>
    </hgroup>
    <div id="goods_image">
        <figure>
            <img alt="HTML5 &amp; CSS3" src="${contextPath}/thumbnails.do?goods_id=${goods.goods_id}&fileName=${goods.goods_fileName}">
        </figure>
    </div>
    <div id="detail_table">
        <table>
            <tbody>
                <tr>
                    <td class="fixed">정가</td>
                    <td class="active"><span>
                            <fmt:formatNumber value="${goods.goods_price}" type="number" var="goods_price" />
                            ${goods_price}원
                        </span></td>
                </tr>
                <tr class="dot_line">
                    <td class="fixed">판매가</td>
                    <td class="active"><span>
                            <fmt:formatNumber value="${goods.goods_price*0.9}" type="number" var="discounted_price" />
                            ${discounted_price}원(10%할인)
                        </span></td>
                </tr>
                <tr>
                    <td class="fixed">포인트적립</td>
                    <td class="active">${goods.goods_point}P(10%적립)</td>
                </tr>
                <tr class="dot_line">
                    <td class="fixed">포인트 추가적립</td>
                    <td class="fixed">만원이상 구매시 1,000P, 5만원이상 구매시 2,000P추가적립 편의점 배송 이용시 300P 추가적립</td>
                </tr>
                <tr>
                    <td class="fixed">발행일</td>
                    <td class="fixed">
                        <c:set var="pub_date" value="${goods.goods_published_date}" />
                        <c:set var="arr" value="${fn:split(pub_date,' ')}" />
                        <c:out value="${arr[0]}" />
                    </td>
                </tr>
                <tr>
                    <td class="fixed">페이지 수</td>
                    <td class="fixed">${goods.goods_total_page}쪽</td>
                </tr>
                <tr class="dot_line">
                    <td class="fixed">ISBN</td>
                    <td class="fixed">${goods.goods_isbn}</td>
                </tr>
                <tr>
                    <td class="fixed">배송료</td>
                    <td class="fixed"><strong>무료</strong></td>
                </tr>
                <tr>
                    <td class="fixed">배송안내</td>
                    <td class="fixed"><strong>[당일배송]</strong> 당일배송 서비스 시작!<br> <strong>[휴일배송]</strong>
                        휴일에도 배송받는 Bookshop</TD>
                </tr>
                <tr>
                    <td class="fixed">도착예정일</td>
                    <td class="fixed">지금 주문 시 내일 도착 예정</td>
                </tr>
                <tr>
                    <td class="fixed">수량</td>
                    <td class="fixed">
                        <select style="width: 60px;" id="order_goods_qty">
                            <option>1</option>
                            <option>2</option>
                            <option>3</option>
                            <option>4</option>
                            <option>5</option>
                        </select>
                    </td>
                </tr>
            </tbody>
        </table>
        <ul>
            <li><a class="buy" href="javascript:fn_order_each_goods('${goods.goods_id }','${goods.goods_title }','${goods.goods_sales_price}','${goods.goods_fileName}');">구매하기 </a></li>
            <li><a class="cart" href="javascript:add_cart('${goods.goods_id }')">장바구니</a></li>

            <li><a class="wish" href="#">위시리스트</a></li>
        </ul>
    </div>
    <div class="clear"></div>
    <!-- 내용 들어 가는 곳 -->
    <div id="container">
        <ul class="tabs">
            <li><a href="#tab1">책소개</a></li>
            <li><a href="#tab2">저자소개</a></li>
            <li><a href="#tab3">책목차</a></li>
            <li><a href="#tab4">출판사서평</a></li>
            <li><a href="#tab5">추천사</a></li>
            <li><a href="#tab6">리뷰</a></li>
        </ul>
        <div class="tab_container">
            <div class="tab_content" id="tab1">
                <h4>책소개</h4>
                <p>${fn:replace(goods.goods_intro,crcn,br)}</p>
                <c:forEach var="image" items="${imageList }">
                    <img src="${contextPath}/download.do?goods_id=${goods.goods_id}&fileName=${image.fileName}">
                </c:forEach>
            </div>
            <div class="tab_content" id="tab2">
                <h4>저자소개</h4>
                <p>
                <div class="writer">저자 : ${goods.goods_writer}</div>
                <%-- fn:replace 함수를 이용해 저자 소개에 포함된 crcn(개행문자)을 br태그로 대체한다. --%>
                <p>${fn:replace(goods.goods_writer_intro,crcn,br) }</p>
            </div>
            <div class="tab_content" id="tab3">
                <h4>책목차</h4>
                <%-- 마찬가지로 상품 목차에 포함된 crcn(개행문자)을 br 태그로 대체한다. --%>
                <p>${fn:replace(goods.goods_contents_order,crcn,br)}</p>
            </div>
            <div class="tab_content" id="tab4">
                <h4>출판사서평</h4>
                <%-- 상품 목차에 포함된 crcn(개행문자)을 br 태그로 대체한다. --%>
                <p>${fn:replace(goods.goods_publisher_comment ,crcn,br)}</p>
            </div>
            <div class="tab_content" id="tab5">
                <h4>추천사</h4>
                <%-- 상품 추천평에 포함된 crcn(개행문자)을 br 태그로 대체한다. --%>
                <p>${fn:replace(goods.goods_recommendation,crcn,br) }</p>
            </div>
            <div class="tab_content" id="tab6">
                <h4>리뷰</h4>
            </div>
        </div>
    </div>
    <div class="clear"></div>
    <div id="layer" style="visibility: hidden">
        <!-- visibility:hidden 으로 설정하여 해당 div안의 모든것들을 가려둔다. -->
        <div id="popup">
            <!-- 팝업창 닫기 버튼 -->
            <a href="javascript:" onClick="javascript:imagePopup('close', '.layer01');"> <img src="${contextPath}/resources/image/close.png" id="close" />
            </a> <br />
            <font size="12" id="contents">장바구니에 담았습니다.</font><br>
            <form action='${contextPath}/cart/myCartList.do'>
                <input type="submit" value="장바구니 보기">
            </form>
</body>

</html>
<input type="hidden" name="isLogOn" id="isLogOn" value="${isLogOn}" />

7. 이번에는 최근 본 상품 이미지를 표시하는 quickMenu.jsp를 작성한다. '최근 본 상품'은 상품 목록에서 상품 정보를 가져온 다음 첫 번째 상품 이미지만 표시하고 다른 상품 이미지는 <hidden> 태그에 저장한다(동일한 <hidden> 태그에 여러 개의 데이터 저장 시 자동으로 배열로 저장된다). 사용자가 다음을 클릭하면 <hidden> 태그의 상품 정보를 자바스크립트 함수로 전달하여 이미지를 표시한다.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"
    isELIgnored="false"
    %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />

<script>
    var array_index = 0;
    var SERVER_URL = "${contextPath}/thumbnails.do";

    function fn_show_next_goods() {
        var img_sticky = document.getElementById("img_sticky");
        var cur_goods_num = document.getElementById("cur_goods_num");
        var _h_goods_id = document.frm_sticky.h_goods_id;
        var _h_goods_fileName = document.frm_sticky.h_goods_fileName;
        if (array_index < _h_goods_id.length - 1) // 다음 클릭 시 배열의 인덱스를 1 증가시킨다.
            array_index++;

        var goods_id = _h_goods_id[array_index].value;
        var fileName = _h_goods_fileName[array_index].value;
        img_sticky.src = SERVER_URL + "?goods_id=" + goods_id + "&fileName=" + fileName;
        cur_goods_num.innerHTML = array_index + 1; // 증가된 인덱스에 대한 배열 요소의 상품 번호외 이미지 파일 이름을 가져와 표시한다.
    } // 빠른 메뉴의 다음 클릭 시 <hidden> 태그에 저장된 상품 정보를 가져와 이미지를 표시한다.


    function fn_show_previous_goods() {
        var img_sticky = document.getElementById("img_sticky");
        var cur_goods_num = document.getElementById("cur_goods_num");
        var _h_goods_id = document.frm_sticky.h_goods_id;
        var _h_goods_fileName = document.frm_sticky.h_goods_fileName;

        if (array_index > 0)
            array_index--;

        var goods_id = _h_goods_id[array_index].value;
        var fileName = _h_goods_fileName[array_index].value;
        img_sticky.src = SERVER_URL + "?goods_id=" + goods_id + "&fileName=" + fileName;
        cur_goods_num.innerHTML = array_index + 1;
    }

    function goodsDetail() {
        var cur_goods_num = document.getElementById("cur_goods_num");
        arrIdx = cur_goods_num.innerHTML - 1;

        var img_sticky = document.getElementById("img_sticky");
        var h_goods_id = document.frm_sticky.h_goods_id;
        var len = h_goods_id.length;

        if (len > 1) {
            goods_id = h_goods_id[arrIdx].value;
        } else {
            goods_id = h_goods_id.value;
        }


        var formObj = document.createElement("form");
        var i_goods_id = document.createElement("input");

        i_goods_id.name = "goods_id";
        i_goods_id.value = goods_id;

        formObj.appendChild(i_goods_id);
        document.body.appendChild(formObj);
        formObj.method = "get";
        formObj.action = "${contextPath}/goods/goodsDetail.do?goods_id=" + goods_id;
        formObj.submit();


    }
</script>

<body>
    <div id="sticky">
        <ul>
            <li><a href="#">
                    <img width="24" height="24" src="${contextPath}/resources/image/facebook_icon.png">
                    페이스북
                </a></li>
            <li><a href="#">
                    <img width="24" height="24" src="${contextPath}/resources/image/twitter_icon.png">
                    트위터
                </a></li>
            <li><a href="#">
                    <img width="24" height="24" src="${contextPath}/resources/image/rss_icon.png">
                    RSS 피드
                </a></li>
        </ul>
        <div class="recent">
            <h3>최근 본 상품</h3>
            <ul>
                <!--   상품이 없습니다. -->
                <c:choose>
                    <c:when test="${ empty quickGoodsList }">
                        <strong>상품이 없습니다.</strong>
                    </c:when>
                    <c:otherwise>
                        <form name="frm_sticky">
                        	<%-- 세션에 저장된 빠른 메뉴 목록이 이미지 정보를 <hidden> 태그에 차례대로 저장한다. --%>
                            <c:forEach var="item" items="${quickGoodsList }" varStatus="itemNum">
                                <c:choose>
                                    <c:when test="${itemNum.count==1 }">
                                        <a href="javascript:goodsDetail();">
                                            <img width="75" height="95" id="img_sticky" src="${contextPath}/thumbnails.do?goods_id=${item.goods_id}&fileName=${item.goods_fileName}">
                                        </a>
                                        <%-- 동일한 <hidden> 태그에 연속해서 저장하면 배열로 저장된다. --%>
                                        <input type="hidden" name="h_goods_id" value="${item.goods_id}" />
                                        <input type="hidden" name="h_goods_fileName" value="${item.goods_fileName}" />
                                        <br>
                                    </c:when>
                                    <c:otherwise>
                                        <input type="hidden" name="h_goods_id" value="${item.goods_id}" />
                                        <input type="hidden" name="h_goods_fileName" value="${item.goods_fileName}" />
                                    </c:otherwise>
                                </c:choose>
                            </c:forEach>
                    </c:otherwise>
                </c:choose>
            </ul>
            </form>
        </div>
        <div>
            <c:choose>
                <c:when test="${ empty quickGoodsList }">
                    <h5> &nbsp; &nbsp; &nbsp; &nbsp; 0/0 &nbsp; </h5>
                </c:when>
                <c:otherwise>
                    <h5><a href='javascript:fn_show_previous_goods();'> 이전 </a> &nbsp; <span id="cur_goods_num">1</span>/${quickGoodsListNum} &nbsp; <a href='javascript:fn_show_next_goods();'> 다음 </a> </h5>
                </c:otherwise>
            </c:choose>
        </div>
    </div>
</body>

</html>

8. 실행 결과를 보자. 상품 상세를 요청하면 다음과 같이 페이지 오른쪽에 있는 빠른 메뉴에 최근 본 상품이 추가된다.

- http://localhost/bookshop01/main/main.do

9. 빠른 메뉴에서 다음을 클릭하면 두 번째 상품 이미지가 표시된다.