감 잃지말고 개발하기

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #4. FAQ 페이지 구현 본문

JSP/MVC

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #4. FAQ 페이지 구현

persii 2023. 4. 19. 16:44

교보문고 고객센터 페이지를 모델로 하여 MVC패턴을 지키면서 ajax를 이용한 비동기 통신으로 DB에 저장된 데이터를 브라우저에 출력하는 로직을 기록하고자 한다.

 

지난 포스팅에서는 고객센터 메인 페이지 및 FAQ 페이지에서 중요한 HTML 코드를 살펴보았고, FAQ 페이지가 요청되는 두 가지 방식을 정리해 보았다.

이번 포스팅에서는 두 가지 방식 중 첫 번째 방식의 로직을 기록해 보겠다.

 

목표

 JSP에서 MVC 패턴을 지키면서 AJAX를 통해 데이터를 출력할 수 있다.

 교보문고 고객센터 페이지를 구현할 수 있다.

 

 

로직 흐름

  • 고객센터 메인 페이지에서 대분류를 클릭하면 새로운 URL이 요청되고, 파라미터 fcode가 서버로 넘어간다(index.jsp).
  • 해당 컨트롤러에서 해당 URL을 받아 fcode에 따른 faq 데이터를 가져오는 클래스를 호출한다(BookFrontController.java).
  • Action 클래스에서 비즈니스 로직을 처리하는 클래스를 호출하여 DB로부터 데이터를 가져오고, 가져온 데이터를 request 내장 객체에 속성으로 공유한 후, 포워딩 처리를 한다(BookCscenterFaqAction.java).
    • 화면 이동 방식은 RequesetDispatcher를 쓴다.
    • 이동할 jsp페이지는 cscenter/faq.jsp이다.
  • Service 클래스에서 DAO클래스를 호출한다(BookCscenterFaqService.java).
  • DAO 클래스에서 MySQL8.0 DB에 접근해 데이터를 가져온다(FaqDAO, FaqKeyDAO).
  • 이동한 FAQ 페이지의 하단 영역에 데이터가 출력된다.

 

 

로직 흐름 코드 및 실행화면

1. 고객센터 메인 페이지에서 대분류 클릭

1-1.  고객센터 메인 페이지

 

1-2.  고객센터 메인 페이지 대분류 영역 HTML

<!-- 대분류 영역 -->
<section class="features-area section_gap">
  <div class="container">
    <div class="container-header">
       <h4><b>자주 묻는 질문</b></h4>
    </div>
    <div class="features-inner text-center" style="padding: 20px 0 !important;"> 
      <div class="row row-cols-4 mx-2">	
        <div class="col-3 p-2 border">	
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok"><h5>Best 10</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=1"><h5>회원</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=2"><h5>도서/상품정보/교과서</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=3"><h5>주문/결제</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=4"><h5>배송/수령일 안내</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=5"><h5>반품/교환/환불</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=6"><h5>세금계산서/증빙</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=7"><h5>서비스</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=8"><h5>eBook</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=9"><h5>중고장터</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=10"><h5>POD 주문형출판</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=11"><h5>PubPle(퍼플)</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=12"><h5>sam</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2" style="list-style-type : none; ">
            <a href="${request.getContextPath() }/cscenter/faq.ok?fcode=13"><h5>북모닝</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-1" style="list-style-type : none; ">
            <a href="#" onclick="return false"><h5></h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-1" style="list-style-type : none; ">
            <a href="#" onclick="return false"><h5></h5></a>
          </li>
        </div>
      </div>
    </div>
  </div>
</section>
<!-- 대분류 영역 -->

 

2. 컨트롤러(BookFrontController.java)

웹 브라우저에서 요청한 URL인 /cscenter/faq.ok에 해당하는 if문으로 들어오고 BookCscenterFaqAction 클래스의 execute() 메서드가 호출된다. 

package controller;

import java.io.IOException;
import java.rmi.ServerException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import action.Action;
import action.BookCscenterFaqAction;
import vo.ActionForward;

@WebServlet("*.ok")
public class BookFrontController extends HttpServlet{
	
	protected void doProcess(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		req.setCharacterEncoding("UTF-8");

		String requestURI = req.getRequestURI();
		String contextPath = req.getContextPath();
		String command = requestURI.substring(contextPath.length());
		System.out.println(" B.Front.C : 1. 요청 주소 계산 완료");
		
		/* 2. 각 요청 주소의 매핑 처리 */
		ActionForward forward = null;
		Action action = null;
		
		// 고객센터 faq 페이지 요청
		if (command.equals("/cscenter/faq.ok")) {
			try {
				action = new BookCscenterFaqAction();
				forward = action.execute(req, resp);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		/* 3. 포워딩 처리 */
		if(forward != null) {
			if(forward.isRedirect()) {
				// redirect 방식
				resp.sendRedirect(forward.getPath());
			} else {
				// forward 방식
				RequestDispatcher dispatcher = req.getRequestDispatcher(forward.getPath());
				dispatcher.forward(req, resp);
			}
		}	
	}

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}
}

 

3. Action 클래스(BookCscenterFaqAction .java)

  • URL로 넘어온 파라미터를 저장한다.
    • 메인 페이지에서 'Best10'을 클릭하면 fcode라는 파라미터는 넘어오지 않기 때문에 그에 따른 처리를 해준다. Best10을 클릭하면 변수 fcode에는 0이 저장될 것이다.
  • Service 클래스를 호출해 DB로부터 데이터를 가져와 ArrayList 타입의 변수에 각각 저장해 준다.
    • 대분류 'Best 10'에 따른 중분류는 존재하지 않는다. 따라서 이 경우에는 중분류를 저장할 ArrayList 변수에  "Best10"과 fcode(0)을 저장해 준다. 
    • 대분류에 따른 중분류 키워드 리스트 → fkList 변수에 저장
    • 대분류에 따른 모든 FAQ 리스트 → faqList 변수에 저장
  • 이동할 URL과 페이지 이동 방식을 설정한 후 리턴한다.   
    • request 내장 객체에 fkList변수와 faqList 변수를 속성으로 공유한다.
package action;

import java.util.ArrayList;

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

import svc.BookCscenterFaqService;
import vo.ActionForward;
import vo.Faq;
import vo.FaqKeyword;

/** fcode에 따른 페이지 요청을 처리하는 Action 클래스 */ 
public class BookCscenterFaqAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		System.out.println(" B.CscenterFaq.A : execute() 호출");
		
		/* 파라미터 저장 */
		int fcode = 0; 
		if(req.getParameter("fcode") != null) {
			fcode = Integer.parseInt(req.getParameter("fcode"));
		}
		
		/* DB에서 fcode에 따른 데이터 가져오기 */
		BookCscenterFaqService service = new BookCscenterFaqService();
		ArrayList<FaqKeyword> fkList = new ArrayList<>();
		if(fcode != 0) {
			fkList = service.selectFks(fcode);
		} else {
			fkList.add(new FaqKeyword("Best10", fcode));
		}
		
		ArrayList<Faq> faqList = service.selectFaq(fcode);
		
		/* 포워딩할 때 가져갈 정보 저장 
		 * 1. faq 키워드를 속성으로 공유
		 * 2. faq를 속성으로 공유 */
		req.setAttribute("fkList", fkList);
		req.setAttribute("faqList", faqList);
		
		ActionForward forward = new ActionForward();
		forward.setPath("/cscenter/faq.jsp");
		forward.setRedirect(false);
		return forward;
	}

}

 

4. Service 클래스(BookCscenterFaqService.java)

위 Action 클래스에서의 처리로 인해 중분류 ArrayList 객체를 가져오는 selectFks() 메서드의 인자 fcode에는 0이 들어오지 않는다.

하지만 fcode가 0인, 즉 대분류가 'Best 10'인 경우, 조회수가 높은 10개의 FAQ 데이터를 가져와야 하므로 FAQ ArrayList 객체를 가져오는 selectFaq() 메서드의 인자 fcode에는 0이 들어갈 수 있다.

package svc;

import static db.JdbcUtil.close;
import static db.JdbcUtil.getConnection;

import java.sql.Connection;
import java.util.ArrayList;

import dao.FaqDAO;
import dao.FaqKeyDAO;
import vo.Faq;
import vo.FaqKeyword;

/** fcode에 따른 비즈니스 로직을 구현하는 Service 클래스*/
public class BookCscenterFaqService {

	/** fcode에 따른 중분류를 ArrayList 객체 타입으로 가져오는 메서드 */
	public ArrayList<FaqKeyword> selectFks(int fcode) {

		FaqKeyDAO faqkeyDAO = FaqKeyDAO.getInstance();
		Connection conn = getConnection();
		faqkeyDAO.setConnection(conn);
		ArrayList<FaqKeyword> fkList = faqkeyDAO.selectFkList(fcode);
		close(conn);

		return fkList;
	}

	/** fcode에 따른 FAQ 데이터를 ArrayList 객체 타입으로 반환하는 메서드 */
	public ArrayList<Faq> selectFaq(int fcode) {

		FaqDAO faqDAO = FaqDAO.getInstance();
		Connection conn = getConnection();
		faqDAO.setConnection(conn);
		ArrayList<Faq> faqList = faqDAO.selectFaqList(fcode);
		close(conn);

		return faqList;
	}
}

 

5. DAO 클래스

각 VO 클래스 내용은 포스팅 #2. 구현 틀 잡기를 참고하면 된다.


2023.04.18 - [JSP/MVC] - [JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #2. 구현 틀 잡기

 

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #2. 구현 틀 잡기

교보문고 고객센터 페이지를 모델로 하여 MVC패턴을 지키면서 ajax를 이용한 비동기 통신으로 DB에 저장된 데이터를 브라우저에 출력하는 로직을 기록하고자 한다. 지난 포스팅에서는 교보문고

persimmon-ary-stepbystep.tistory.com


5-1.  중분류 DAO 클래스(FaqKeyDAO.java)

package dao;

import static db.JdbcUtil.*;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

import vo.Faq;
import vo.FaqKeyword;

public class FaqKeyDAO {

	Connection conn;
	private static FaqKeyDAO faqKeyDAO;
	
	private FaqKeyDAO() {
	}
	
	public static FaqKeyDAO getInstance() {
		if(faqKeyDAO == null) {
			faqKeyDAO = new FaqKeyDAO();
		}
		return faqKeyDAO;
	}
	
	/** FaqKeyDAO 객체에 Connection 객체를 주입하는 메서드 */
	public void setConnection(Connection conn) {
		this.conn = conn;
	}
	
	/** fcode에 따른 중분류를 ArrayList 객체 타입으로 가져오는 메서드 */
	public ArrayList<FaqKeyword> selectFkList(int fcode) {

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		ArrayList<FaqKeyword> fkList = null;
		
		/* 2개의 테이블 조인하여 fc_code=?인 값에 따른 대분류 정보와 중분류 정보를 가져오는 qeury */
		String sql = "select fc.fc_code, fc.fc_value, fk.fk_code, fk.fk_value "
					+ "from faqcode fc join faqkeyword fk "
					+ "on fc.fc_code = fk.fk_fc_code "
					+ "and fc.fc_code = "+fcode;
		
		try {
			pstmt = conn.prepareStatement(sql);
			rs = pstmt.executeQuery();
			
			if (rs.next()) {
				fkList = new ArrayList<FaqKeyword>();
				
				do {
					fkList.add(new FaqKeyword(rs.getInt(1), 
								rs.getString(2),
								rs.getInt(3),
								rs.getString(4)));
				} while (rs.next());
			}
		} catch (Exception e) {
			System.out.println(" Fk.DAO : selectFkList ERROR : "+e);
		} finally {
			close(rs);
			close(pstmt);
		}
		return fkList;
	}
}

 

5-2.  FAQ DAO 클래스(FaqDAO.java)

selectFaqList() 메서드의 인자 fcode은 0부터 13(대분류 개수)까지 들어올 수 있으므로 0인 경우의 쿼리문과 그 외의 쿼리문을 따로 만들어주었다. 

조건문 and f.f_use = 1 은 사용 중인 FAQ만 가져오도록 한 것인데 딱히 신경 쓸 부분은 아니다. 

package dao;

import static db.JdbcUtil.*;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

import vo.Faq;

public class FaqDAO {

	Connection conn;
	private static FaqDAO faqDAO;
	
	private FaqDAO() {
	}

	/** 싱글톤 패턴으로 FaqDAO 객체를 생성하여 리턴하는 메서드 */
	public static FaqDAO getInstance() {
		if(faqDAO == null) {
			faqDAO = new FaqDAO();
		}
		return faqDAO;
	}
    
	public void setConnection(Connection conn) {
		this.conn = conn;
	}
	
	/** fcode에 따른 faq 목록을 가져오는 메서드 */
	public ArrayList<Faq> selectFaqList(int fcode) {

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		ArrayList<Faq> faqList = null;
		
		// fcode == 0(Best10)인 경우
		String sql = "select f.f_id, f.f_title, f.f_text, fk.fk_code, fk.fk_value "
					+ "from jspbookshop.faqkeyword fk join jspbookshop.faq f "
					+ "on f.f_fk_code = fk.fk_code "
					+ "and f.f_use = 1 "
					+ "order by f.f_readcnt desc, fk.fk_fc_code asc "
					+ "limit 10";
		
		if(fcode != 0) {
			sql = "select f.f_id, f.f_title, f.f_text, fk.fk_code, fk.fk_value "
					+ "from jspbookshop.faqkeyword fk join jspbookshop.faq f "
					+ "on f.f_fk_code = fk.fk_code "
					+ "and fk.fk_fc_code = "+fcode+" "
					+ "and f.f_use = 1 "
					+ "order by f.f_readcnt desc, fk.fk_fc_code asc";
		}

		try {
			pstmt = conn.prepareStatement(sql);
			rs = pstmt.executeQuery();
			
			if (rs.next()) {
				faqList = new ArrayList<Faq>();
				
				do {
					faqList.add(new Faq(rs.getInt(1), 
                    					rs.getInt(4), 
							rs.getString(2), 
							rs.getString(3), 
          						rs.getString(5)));
				} while (rs.next());
			}
		} catch (Exception e) {
			System.out.println(" F.DAO : selectFaqList ERROR : "+e);
		} finally {
			close(rs);
			close(pstmt);
		}
		return faqList;
	}
}

 

6. FAQ 페이지

6-1.  이동한 FAQ 페이지 화면

FAQ 페이지

 

6-2.  FAQ 페이지 데이터 출력 영역 코드

fkList의 길이가 1인 경우, 즉 Best10을 클릭한 경우 대분류명만 출력하게 만들었다. 

<!-- 대분류 -->
<section class="features-area section_gap">
  <div class="container">
    <div class="container-header">
      <h3><b>자주 묻는 질문</b></h3>
    </div>
  </div>
</section>
<!-- 대분류 -->

<!-- faq.ok?fcode=n에 따른 데이터 출력 영역 -->
<section class="container mb-5 faqSection">
  <div class="container-header faqTabArea" style="padding:10px 0;">
    <c:if test="${fkList != null and fn:length(fkList) eq 1}">
      <h3><b>${fkList[0].fc_value}</b></h3>	
    </c:if>
    <c:if test="${fkList != null and fn:length(fkList) > 1}">
      <h3><b>${fkList[0].fc_value}</b></h3>	
      <div style="height: 50px;">
        <ul class="nav h-100 faqkeywordUl">
          <li class="nav-item my-2 mr-2">
            <button type="button" class="fkbtn mh-49 p-2 border border-warning bg-white text-dark" id="tab00" 
                    style="border-radius: 20px !important; font-size: 1rem; line-height: 1.5;">
              <span class="text-center">전체</span>
            </button>
          </li>
          <c:forEach var="fk" items="${fkList}" varStatus="status">
            <li class="nav-item my-2 mr-2">
              <button type="button" class="fkbtn mh-49 p-2 border border-warning bg-white text-dark" id="tab0${status.index+1 }" 
                      style="border-radius: 20px !important; font-size: 1rem; line-height: 1.5;">
                <span class="text-center">${fk.fk_value }</span>
              </button>
            </li>
          </c:forEach>
        </ul>
      </div>
    </c:if>
    <hr class="my-3">
  </div>

  <div class="accordion faqCardList" id="accordionExample">
    <c:forEach var="faq" items="${faqList}" varStatus="status">
      <div class="card">
        <div class="card-header" id="heading${status.index}" style="background-color:white !important;"> 
          <h2 class="mb-0">
            <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" 
                    data-target="#collapse${status.index}" aria-expanded="true" aria-controls="collapse${status.index}" 
                    style="color:black !important;">
               [${faq.fk_value}] ${faq.f_title}
            </button>
          </h2>
        </div>
        <div id="collapse${status.index}" class="collapse" aria-labelledby="heading${status.index}" data-parent="#accordionExample">
          <div class="card-body" style="background-color:rgba(0,0,0,.03);">
                  ${faq.f_text}
          </div>
        </div>
      </div>
    </c:forEach>
  </div>
</section>
<!-- faq.ok?fcode=n에 따른 데이터 출력 영역 -->

 

 

이렇게 MVC 패턴의 첫 번째 로직이 구현되었다.

다음 시간에는 나머지 두 번째 방식의 로직을 정리해 보겠다.

 

 

끝.