관리 메뉴

거니의 velog

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

Java_Spring mini project 쇼핑몰

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

Unlimited00 2023. 11. 23. 09:28

2. 도서 쇼핑몰 기능 알아보기

* 어떤 종류의 쇼핑몰이든 고객들이 가장 자주 사용하는 기능들을 다음 표로 정리해 보았다.

< 쇼핑몰에 구현된 기능 >

기능 세부기능
메인 페이지 메인 페이지
빠른 메뉴
회원 기능 로그인
회원 가입
상품 기능 상품 검색
장바구니 기능 장바구니 상품 조회
장바구니 상품 추가
장바구니 상품 수정
장바구니 상품 삭제
주문 기능 상품 주문
주문 상품 조회
주문 수정
주문 취소
마이페이지 기능 주문 내역 조회
회원 정보 수정
회원 탈퇴

* 지금부터 각 기능을 차례대로 알아보자. 먼저 쇼핑몰의 프로젝트 및 데이터베이스 구조를 살펴보자.


(1) 쇼핑몰 프로젝트 및 데이터베이스 구조

* 먼저 우리가 만들 도서 쇼핑몰의 데이터베이스 구조를 살펴보자.

* 크게 5개의 테이블(회원정보, 주문테이블, 상품정보, 상품이미지정보, 장바구니)로 이루어져 있다. 각 테이블과 관련된 기능은 실습하면서 차차 설명하도록 한다.

* 이 프로젝트에서 사용할 Controller 클래스들의 계층 구조를 다음 그림에 나타내었다.

* 모든 Controller 클래스들은 BaseController 클래스를 상속받는 것을 볼 수 있다. 각각의 구현 클래스(*** ControllerImpl)에서 사용할 메서드를 ***Controller 인터페이스에 선언한다.

* 또한 다음 그림에서는 Service와 DAO 클래스의 계층 구조를 나타내었다.

* 이제는 도서 쇼핑몰 중 메인 페이지와 상품 조회 기능을 구현하는 bookshop01 프로젝트의 소스 구조를 살펴보자.

* 이 프로젝트에 사용되는 CSS, 이미지 파일, 제이쿼리, 자바스크립트 파일 등은 모두 src/main/webapp/resources 폴더에 있다.

* 톰캣을 실행하면 servlet-context.xml에서 src/main/webapp/resources의 CSS, 이미지 파일, 제이쿼리, 자바스크립트 파일을 읽어 들여 설정한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- /resources/ 폴더의 HTML, 자바스크립트, 제이쿼리, 이미지들을 읽어들여 설정한다. -->
	<resources mapping="/resources/**" location="/resources/" />
	
	<!-- 타일즈 기능을 설정한다. -->
	<beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
		<beans:property name="definitions">
			<beans:list>
				<beans:value>classpath:tiles/*.xml</beans:value>
			</beans:list>
		</beans:property>
		<beans:property name="preparerFactoryClass" value="org.springframework.web.servlet.view.tiles2.SpringBeanPreparerFactory"/>
	</beans:bean>
	<beans:bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> 
		<beans:property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/> 
	</beans:bean>
	<context:component-scan base-package="com.bookshop01" />
	
	<!-- 2단계와 3단계 요청에 대해 인터셉터를 설정한다. -->
	<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/*/*.do"/> 
			<mvc:mapping path="/*/*/*.do"/> 
			<beans:bean class="com.bookshop01.common.interceptor.ViewNameInterceptor" />
		</mvc:interceptor>
	</mvc:interceptors>
 
 	<!-- 멀티파트 뷰리졸버를 설정한다. -->
    <beans:bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
       <beans:property name="maxUploadSize" value="52428800" />
       <beans:property name="maxInMemorySize" value="52428800" />
       <beans:property name="defaultEncoding" value="utf-8" />
    </beans:bean>
 
</beans:beans>

3. 메인 페이지 구현하기

* 우리가 만들 도서 쇼핑몰 메인 페이지에는 신간, 베스트셀러, 스테디셀러가 다음과 같이 한 줄에 4권씩 표시된다.

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

* 또한 메인 페이지와 관련된 테이블을 보면 상품정보 테이블과 상품이미지정보 테이블의 데이터를 조회한 후 메인 페이지에 표시한다는 것을 알 수 있다.

* 그럼 본격적으로 진행해보자. 먼저 매퍼 파일을 구현한다.

1. 메인 페이지에 표시할 상품을 조회하는 SQL문에 대한 매퍼 파일인 goods.xml을 준비한다.

2. goods.xml에서 조건 값을 베스트셀러, 신간, 스테디셀러로 지정하여 각각 상품 정보와 이미지 정보를 15개씩 조회한다.

<mapper namespace="mapper.goods">

	<!-- 상품 정보를 저장하는 resultMap이다. -->
	<resultMap id="goodsResult" type="goodsVO">
		<result property="goods_id" column="goods_id" />
		<result property="goods_title" column="goods_title" />
		<result property="goods_writer" column="goods_writer" />
		<result property="goods_price" column="goods_price" />
		<result property="goods_publisher" column="goods_publisher" />
	</resultMap>
    
    <!-- 상품 이미지 정보를 저장하는 resultMap이다. -->
    <resultMap id="imageResult" type="ImageFileVO">
	   <result property="goods_id" column="goods_id" />
	   <result property="fileName" column="fileName" />
	   <result property="reg_id" column="reg_id" />
	   <result property="image_id" column="image_id" />
	   <result property="fileType" column="fileType" />
	</resultMap>
    
    <!-- goods_status의 조건 값으로 '신상품', '베스트셀러', '스테디셀러'를 전달해 각각의 상품을 15개까지 조회한다. -->
    <select id="selectGoodsList" parameterType="String" resultMap="goodsResult"   >
        <![CDATA[
         select t.*
          from(
			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 goods_status=#{goodsStatus}
            order by g.goods_creDate desc)  t
	             where   rowNum <16
	    ]]>
	</select>

3. 다음으로 메인 페이지를 나타낼 타일즈를 설정하는 tiles_main.xml 파일을 생성한다.

4. tiles_main.xml을 다음과 같이 작성한다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
<tiles-definitions>
	<!-- 쇼핑몰 기본 레이아웃을 설정한다. -->
	<definition name="baseLayout"
		template="/WEB-INF/views/common/layout.jsp">
		<put-attribute name="title" value="레이아웃 페이지" />
		<put-attribute name="header" value="/WEB-INF/views/common/header.jsp" />
		<put-attribute name="side" value="/WEB-INF/views/common/side.jsp" />
		<put-attribute name="body" value="" />
		<put-attribute name="footer" value="/WEB-INF/views/common/footer.jsp" />
		<put-attribute name="quickMenu" value="/WEB-INF/views/common/quickMenu.jsp" />
	</definition>

	<!-- 쇼핑몰 메인 페이지 레이아웃을 설정한다. -->
	<definition name="/main/main" extends="baseLayout">
		<put-attribute name="title" value="쇼핑몰 메인페이지" />
		<put-attribute name="body" value="/WEB-INF/views/main/main.jsp" />
	</definition>
</tiles-definitions>

5. 메인 페이지에도 상품 정보가 표시되므로 goods 패키지 기능을 사용한다. 필요한 자바 클래스는 다음과 같다.

6. 메인 컨트롤러인 MainController 클래스에서는 브라우저 요청 시 베스트셀러, 신간, 스테디셀러를 조회한 후 Map에 저장하여 JSP로 전달한다.

package com.bookshop01.main;

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.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

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

@Controller("mainController")
@EnableAspectJAutoProxy
public class MainController extends BaseController {
	@Autowired
	private GoodsService goodsService;

	@RequestMapping(value= "/main/main.do" ,method={RequestMethod.POST,RequestMethod.GET})
	public ModelAndView main(HttpServletRequest request, HttpServletResponse response) throws Exception{
		HttpSession session;
		ModelAndView mav=new ModelAndView();
		String viewName=(String)request.getAttribute("viewName");
		mav.setViewName(viewName);
		
		session=request.getSession();
		session.setAttribute("side_menu", "user"); // 속성, 즉 side_menu의 값에 따라 화면 왼쪽에 표시되는 메뉴 항목을 다르게 한다.
		Map<String,List<GoodsVO>> goodsMap=goodsService.listGoods(); // 베스트셀러, 신간, 스테디셀러 정보를 조회해 Map에 저장한다.
		mav.addObject("goodsMap", goodsMap); // 메인 페이지로 상품 정보를 전달한다.
		return mav;
	}
}

7. Service 클래스인 GoodsServiceImpl에서는 베스트셀러, 신간, 스테디셀러를 SQL문의 조건으로 전달하여 각각의 도서 정보를 조회해서 HashMap에 저장한 후 컨트롤러로 반환한다.

@Service("goodsService")
@Transactional(propagation=Propagation.REQUIRED)
public class GoodsServiceImpl implements GoodsService{
	@Autowired
	private GoodsDAO goodsDAO;
	
	public Map<String,List<GoodsVO>> listGoods() throws Exception {
		Map<String,List<GoodsVO>> goodsMap=new HashMap<String,List<GoodsVO>>();
		List<GoodsVO> goodsList=goodsDAO.selectGoodsList("bestseller");
		goodsMap.put("bestseller",goodsList);
		goodsList=goodsDAO.selectGoodsList("newbook");
		goodsMap.put("newbook",goodsList);
		
		goodsList=goodsDAO.selectGoodsList("steadyseller");
		goodsMap.put("steadyseller",goodsList);
		return goodsMap;  // newbook, bestseller, steadyseller를 조건으로 각각 도서 정보를 조회해서 HashMap에 저장한 후 반환한다.
	}
    
    ...

8. GoodsDAOImpl 클래스를 다음과 같이 작성한다.

@Repository("goodsDAO")
public class GoodsDAOImpl implements GoodsDAO {
	@Autowired
	private SqlSession sqlSession;

	@Override
	public List<GoodsVO> selectGoodsList(String goodsStatus) throws DataAccessException {
		// selectList() 메서드 호출 시 전달된 조건으로 도서 정보를 조회한다.
		List<GoodsVO> goodsList = (ArrayList) sqlSession.selectList("mapper.goods.selectGoodsList", goodsStatus);
		return goodsList;
	}
    
    ...

9. 이제는 각 상품의 이미지를 다운로드하는 컨트롤러를 구현할 차례이다.

10. FileDownloadController 클래스를 다음과 같이 구현한다.

@Controller
public class FileDownloadController {
	private static String CURR_IMAGE_REPO_PATH = "C:\\shopping\\file_repo";
	
	@RequestMapping("/download")
	protected void download(@RequestParam("fileName") String fileName,
		                 	@RequestParam("goods_id") String goods_id, // 이미지 파일 이름과 상품 id를 가져온다.
			                 HttpServletResponse response) throws Exception {
		
		OutputStream out = response.getOutputStream();
		String filePath=CURR_IMAGE_REPO_PATH+"\\"+goods_id+"\\"+fileName;
		File image=new File(filePath);

		response.setHeader("Cache-Control","no-cache");
		response.addHeader("Content-disposition", "attachment; fileName="+fileName);
		
		FileInputStream in=new FileInputStream(image); 
		byte[] buffer=new byte[1024*8];
		
		while(true){
			int count=in.read(buffer); //버퍼에 읽어들인 문자개수
			if(count==-1)  //버퍼의 마지막에 도달했는지 체크
				break;
			out.write(buffer,0,count);
		}
		
		in.close();
		out.close();
	}
	
	@RequestMapping("/thumbnails.do")
	protected void thumbnails(@RequestParam("fileName") String fileName,
                            	@RequestParam("goods_id") String goods_id,
			                 HttpServletResponse response) throws Exception {
		
		OutputStream out = response.getOutputStream();
		String filePath=CURR_IMAGE_REPO_PATH+"\\"+goods_id+"\\"+fileName;
		File image=new File(filePath);
		
		if (image.exists()) { 
			Thumbnails.of(image).size(121,154).outputFormat("png").toOutputStream(out);
		} // 메인 페이지 이미지를 썸네일로 표시한다.
		
		byte[] buffer = new byte[1024 * 8];
		
		out.write(buffer);
		out.close();
	}
    
    ...

11. 마지막으로 메인 페이지와 왼쪽 메뉴를 표시하는 JSP 파일들을 준비한다.

12. main.jsp를 다음과 같이 표시한다.

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

<div id="ad_main_banner">
    <ul class="bjqs">
        <li><img width="775" height="145" src="${contextPath}/resources/image/main_banner01.jpg"></li>
        <li><img width="775" height="145" src="${contextPath}/resources/image/main_banner02.jpg"></li>
        <li><img width="775" height="145" src="${contextPath}/resources/image/main_banner03.jpg"></li>
    </ul>
</div>
<div class="main_book">
    <c:set var="goods_count" value="0" />
    <h3>베스트셀러</h3>
    <%-- 베스트셀러를 표시한다. --%>
    <c:forEach var="item" items="${goodsMap.bestseller }">
        <c:set var="goods_count" value="${goods_count+1 }" />
        <div class="book">
        	<%-- 이미지 클릭 시 상품 상세 페이지로 넘어간다. --%>
            <a href="${contextPath}/goods/goodsDetail.do?goods_id=${item.goods_id }">
                <img class="link" src="${contextPath}/resources/image/1px.gif">
            </a>
            <%-- 썸네일 이미지를 표시한다. --%>
            <img width="121" height="154" src="${contextPath}/thumbnails.do?goods_id=${item.goods_id}&fileName=${item.goods_fileName}">
            <div class="title">${item.goods_title }</div>
            <div class="price">
                <fmt:formatNumber value="${item.goods_price}" type="number" var="goods_price" />
                ${goods_price}원
            </div>
        </div>
        <%-- 베스트셀러가 15개 이상이면 16번째 이미지에는 more를 표시한다. --%>
        <c:if test="${goods_count==15   }">
            <div class="book">
                <font size=20> <a href="#">more</a></font>
            </div>
        </c:if>
    </c:forEach>
</div>
<div class="clear"></div>
<div id="ad_sub_banner">
    <img width="770" height="117" src="${contextPath}/resources/image/sub_banner01.jpg">
</div>
<div class="main_book">
    <c:set var="goods_count" value="0" />
    <h3>새로 출판된 책</h3>
    <c:forEach var="item" items="${goodsMap.newbook }">
        <c:set var="goods_count" value="${goods_count+1 }" />
        <div class="book">
            <a href="${contextPath}/goods/goodsDetail.do?goods_id=${item.goods_id }">
                <img class="link" src="${contextPath}/resources/image/1px.gif">
            </a>
            <img width="121" height="154" src="${contextPath}/thumbnails.do?goods_id=${item.goods_id}&fileName=${item.goods_fileName}">
            <div class="title">${item.goods_title }</div>
            <div class="price">
                <fmt:formatNumber value="${item.goods_price}" type="number" var="goods_price" />
                ${goods_price}원
            </div>
        </div>
        <c:if test="${goods_count==15   }">
            <div class="book">
                <font size=20> <a href="#">more</a></font>
            </div>
        </c:if>
    </c:forEach>
</div>

<div class="clear"></div>
<div id="ad_sub_banner">
    <img width="770" height="117" src="${contextPath}/resources/image/sub_banner02.jpg">
</div>


<div class="main_book">
    <c:set var="goods_count" value="0" />
    <h3>스테디셀러</h3>
    <c:forEach var="item" items="${goodsMap.steadyseller }">
        <c:set var="goods_count" value="${goods_count+1 }" />
        <div class="book">
            <a href="${contextPath}/goods/goodsDetail.do?goods_id=${item.goods_id }">
                <img class="link" src="${contextPath}/resources/image/1px.gif">
            </a>
            <img width="121" height="154" src="${contextPath}/thumbnails.do?goods_id=${item.goods_id}&fileName=${item.goods_fileName}">
            <div class="title">${item.goods_title }</div>
            <div class="price">
                <fmt:formatNumber value="${item.goods_price}" type="number" var="goods_price" />
                ${goods_price}원
            </div>
        </div>
        <c:if test="${goods_count==15   }">
            <div class="book">
                <font size=20> <a href="#">more</a></font>
            </div>
        </c:if>
    </c:forEach>
</div>

13. side.jsp에서는 컨트롤러에서 전달된 side_menu의 값에 따라 사용자, 관리자, 마이페이지 각각의 메뉴 항목을 표시한다.

<%@ 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}" />

<nav>
    <ul>
        <c:choose>
            <%-- 관리자 메뉴를 표시한다. --%>
            <c:when test="${side_menu=='admin_mode' }">
                <li>
                    <H3>주요기능</H3>
                    <ul>
                        <li><a href="${contextPath}/admin/goods/adminGoodsMain.do">상품관리</a></li>
                        <li><a href="${contextPath}/admin/order/adminOrderMain.do">주문관리</a></li>
                        <li><a href="${contextPath}/admin/member/adminMemberMain.do">회원관리</a></li>
                        <li><a href="#">배송관리</a></li>
                        <li><a href="#">게시판관리</a></li>
                    </ul>
                </li>
            </c:when>
            <%-- 마이페이지 메뉴를 표시한다. --%>
            <c:when test="${side_menu=='my_page' }">
                <li>
                    <h3>주문내역</h3>
                    <ul>
                        <li><a href="${contextPath}/mypage/listMyOrderHistory.do">주문내역/배송 조회</a></li>
                        <li><a href="#">반품/교환 신청 및 조회</a></li>
                        <li><a href="#">취소 주문 내역</a></li>
                        <li><a href="#">세금 계산서</a></li>
                    </ul>
                </li>
                <li>
                    <h3>정보내역</h3>
                    <ul>
                        <li><a href="${contextPath}/mypage/myDetailInfo.do">회원정보관리</a></li>
                        <li><a href="#">나의 주소록</a></li>
                        <li><a href="#">개인정보 동의내역</a></li>
                        <li><a href="#">회원탈퇴</a></li>
                    </ul>
                </li>
            </c:when>
            <%-- 그 외 사용자 메뉴를 표시한다. --%>
            <c:otherwise>
                <li>
                    <h3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;국내외 도서</h3>
                    <ul>
                        <li><a href="${contextPath}/goods/goodsList.do">IT/인터넷</a></li>
                        <li><a href="#">경제/경영</a></li>
                        <li><a href="#">대학교재</a></li>
                        <li><a href="#">자기계발</a></li>
                        <li><a href="#">자연과학/공학</a></li>
                        <li><a href="#">역사/인문학</a></li>
                    </ul>
                </li>
                <li>
                    <h3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;음반</h3>
                    <ul>
                        <li><a href="#">가요</a></li>
                        <li><a href="#">록</a></li>
                        <li><a href="#">클래식</a></li>
                        <li><a href="#">뉴에이지</a></li>
                        <li><a href="#">재즈</a></li>
                        <li><a href="#">기타</a></li>
                    </ul>
                </li>
            </c:otherwise>
        </c:choose>
    </ul>
</nav>
<div class="clear"></div>
<div id="banner">
    <a href="#"><img width="190" height="163" src="${contextPath}/resources/image/n-pay.jpg"> </a>
</div>
<DIV id="notice">
    <H2>공지사항</H2>
    <UL>

        <c:forEach var="i" begin="1" end="5" step="1">
            <li><a href="#">공지사항입니다.${ i}</a></li>
        </c:forEach>
    </ul>
</div>


<div id="banner">
    <a href="#"><img width="190" height="362" src="${contextPath}/resources/image/side_banner1.jpg"></a>
</div>
<div id="banner">
    <a href="#"><img width="190" height="104" src="${contextPath}/resources/image/call_center_logo.jpg"></a>
</div>
<div id="banner">
    <a href="#"><img width="190" height="69" src="${contextPath}/resources/image/QnA_logo.jpg"></a>
</div>

</html>

14. 실행하면 화면 왼쪽에 일반 사용자를 위한 메뉴 항목이 표시된다.

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