일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Spring MVC
- json
- jsp 프로젝트
- jakarta.mail
- Spring
- 코딩
- 오라클
- 로그인과 장바구니 구현
- 스프링
- 고객센터 구현
- 인증코드로 비밀번호 변경 구현
- 대분류/중분류/소분류
- ajax
- java
- jquery
- SESSION
- 다중 카테고리 구현
- Level 1
- 프로그래머스
- 자바
- 일단_해보는거야
- 이메일로 인증코드 전송 구현
- 교보문고 따라하기
- 세션
- MVC
- MySQL
- Sts
- jsp
- Oracle
- js
감 잃지말고 개발하기
[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #4. FAQ 페이지 구현 본문
[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 페이지 화면
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 패턴의 첫 번째 로직이 구현되었다.
다음 시간에는 나머지 두 번째 방식의 로직을 정리해 보겠다.