일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- dbms
- Ajax
- mybatis
- c programming
- 웹개발
- html5
- javascript
- 비밀번호찾기
- 로그인
- 미로 생성 알고리즘
- 웹페이지
- 오라클
- 회원가입
- css3
- 네비게이터
- 웹서비스
- 마이바티스
- MVC
- 스프링
- spring
- 프론트엔드
- jQuery
- 백엔드
- Linked List
- 풀스택
- 제이쿼리
- 서블릿
- jsp
- Binding
- 프레임워크
- Today
- Total
Programmer's Progress
도서 정보 제공 웹 서비스 - 게시글 읽기 본문
글을 수정하는 동작은 다음 포스팅에서 다룰 예정이다.
이유를 간단하게 밝히자면, 게시글 자체의 제목, 내용을 DB에서 새로운 내용으로 UPDATE 하는 것 자체는
정말 쉬운 일이지만, 해당 게시글에 첨부된 이미지 파일들의 경우 이것이 기존에 존재하는 이미지 파일인지
아닌지, 기존 이미지 파일을 완전히 삭제하려는 것인지 단순히 수정하려는 것인지 판단하는 것이
상당히 복잡한 과정을 거쳐 이루어지기 때문이다.
만약 게시글당 첨부할 수 있는 이미지 파일이 최대 1개로 고정되어있다면 단순히 글을 수정할 때 업로드하는
이미지 파일의 이름과 서버에 저장된 이미지 파일의 이름이 같은지 다른지 판단해서 처리하면 쉽지만
첨부한 파일이 여러 개인 경우에는 그렇게 할 수가 없기에 방법을 고안하느라 구현 시간도 오래 걸렸다.
게시글 읽기는 다음과 같은 과정으로 이루어진다.
1. 내비게이션 메뉴에서 자유게시판, 정보 게시판, 문의게시판 중 하나를 클릭하면
컨트롤러에 board.jsp 뷰를 요청한다. 이때 어떤 종류의 게시판인지를 나타내는 BOARD값을
GET 방식으로 같이 전달한다.
아래의 코드는 사이드 네비게이션 메뉴를 구성하는 코드의 일부이다.
<div id="mainnav3" class="mainnav active">커뮤니티</div>
<div class="subnav">
<a class="option" href="${path}/board/boardForm.do?BOARD=free_board">자유게시판</a>
<a class="option" href="${path}/board/boardForm.do?BOARD=info_board">정보게시판</a>
<c:if test="${sessionScope.CUSTOMER !=null}">
<a class="option" href="${path}/message/sendMessageForm.do">메세지 보내기</a>
<a class="option" href="${path}/message/receiveMessageForm.do">메세지 보관함</a>
</c:if>
</div>
2. 컨트롤러는 BOARD 종류를 나타내는 파라미터를 전달받아서 모델 뷰 객체에 추가하고
게시글 목록을 보여줄 뷰 이름을 뷰네임리졸버 빈에 전달한다. 해당 빈은 스프링 프레임워크가 자동으로 생성한다.
뷰 이름을 전달받고 특정 뷰를 선택하여 디스 패쳐 서블릿으로 응답하고 나면 게시글 목록 화면이 나타난다.
3. 게시글 목록 뷰는 BOARD값을 전달받아서 JSTL을 통해 title을 동적으로 결정한다.
또한 해당 BOARD값을 게시글 목록을 담을 테이블에 value값으로 담아둔다.
이는 컨트롤러에 게시글을 AJAX 요청할 때 같이 전달하기 위함이다.
아래의 소스코드는 board.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" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<c:set var="path" value="${pageContext.request.contextPath}"/>
<script src="${path}/resources/js/jquery.js"></script>
<script src="${path}/resources/js/jquery-ui.js"></script>
<script src="${path}/resources/js/jquery-cookie.js"></script>
<script src="${path}/resources/js/board.js"></script>
<link rel="stylesheet" href="${path}/resources/css/board.css">
<title>
<c:choose>
<c:when test="${BOARD=='free_board'}">자유게시판</c:when>
<c:when test="${BOARD=='qna_board'}">문의게시판</c:when>
<c:when test="${BOARD=='info_board'}">정보게시판</c:when>
</c:choose>
</title>
</head>
<body>
<jsp:include page="/WEB-INF/views/sidebar.jsp"/>
<div id="container" style="overflow: scroll;">
<div>
<div id="panel">
<select id="FLAG" name="flag">
<option value="CUSTOMER_ID" selected>작성자</option>
<option value="ARTICLE_TITLE">제목</option>
<option value="ARTICLE_CONTENT">내용</option>
</select>
<input id="SEARCH" type="text" name="data">
<input id="SEARCH_BUTTON" type="button" value="검색">
</div>
<table id="listTable" value="${BOARD}">
</table>
</div>
<div id="paging"></div>
<input id="write" type="button" value="글 쓰기">
</div>
</body>
</html>
4. SECTION, PAGE값, BOARD 종류, 검색 기준, 검색할 값 등등을 파라미터로 같이 첨부하여 컨트롤러에
게시글 목록을 요청한다. 굳이 AJAX로 요청하는 가장 큰 이유는 화면의 새로고침 때문에 매번 페이지를
넘길 때마다 다시 스크롤을 일일이 내려야 하는 번거로움을 없애기 위함이다.
function getArticles(SECTION,PAGE){
$.ajax({
"url":"/LibraryService/board/listArticles.do",
"type":"post",
"dataType":"json",
"data":{
"BOARD":BOARD,
"SECTION":SECTION,
"PAGE":PAGE,
"SEARCH":SEARCH,
"FLAG":$("#FLAG").val()
},
"success":function(result){
var TOTAL = result.TOTAL;
if(result.FLAG == "FALSE"||TOTAL==0){
SECTION=1;
PAGE=1;
$("#listTable").empty();
var tr = $("<tr></tr>");
tr.append("<th>번호</th>");
tr.append("<th>작성자</th>");
tr.append("<th>제목</th>");
tr.append("<th>작성일</th>");
tr.append("<th>조회</th>");
$("#listTable").append(tr);
tr = $("<tr></tr>");
tr.append("<td colspan=5>게시글 정보가 존재하지 않습니다.</td>");
$("#listTable").append(tr);
paging(0);
}else{
$("#listTable").empty();
var tr = $("<tr></tr>");
tr.append("<th>번호</th>");
tr.append("<th>작성자</th>");
tr.append("<th>제목</th>");
tr.append("<th>작성일</th>");
tr.append("<th>조회</th>");
$("#listTable").append(tr);
for(var i=0; i<result.LIST.length;i++){
var recent = isRecent(result.LIST[i].ARTICLE_DATE_STRING);
var NEW = recent==true?" <span class='new'>[new]</span>":"";
var temp = result.LIST[i].ARTICLE_DATE_STRING.split(" ");
tr = $("<tr></tr>");
tr.append("<td>"+(i+1)+"</td>");
tr.append("<td>"+result.LIST[i].CUSTOMER_ID+"</td>");
if(result.LIST[i].LVL>1){
tr.append("<td style='text-indent:"+((result.LIST[i].LVL-1)*20)+"px;'><a href='/LibraryService/board/viewArticle.do?BOARD="+BOARD+"&ARTICLE_ID="+result.LIST[i].ARTICLE_ID+"'> ➥ "+result.LIST[i].ARTICLE_TITLE+NEW+"</a></td>");
}else{
tr.append("<td style='text-indent:"+((result.LIST[i].LVL-1)*20)+"px;'><a href='/LibraryService/board/viewArticle.do?BOARD="+BOARD+"&ARTICLE_ID="+result.LIST[i].ARTICLE_ID+"'>"+result.LIST[i].ARTICLE_TITLE+NEW+"</a></td>");
}
if(recent){
tr.append("<td>"+temp[1]+"</td>");
}else{
tr.append("<td>"+temp[0]+"</td>");
}
tr.append("<td>"+result.LIST[i].ARTICLE_VIEWS+"</td>");
$("#listTable").append(tr);
}
paging(TOTAL);
}
},
"error":function(a,b,c){
alert("에러");
SECTION=1;
PAGE=1;
$("#listTable").empty();
var tr = $("<tr></tr>");
tr.append("<th>번호</th>");
tr.append("<th>작성자</th>");
tr.append("<th>제목</th>");
tr.append("<th>작성일</th>");
tr.append("<th>조회</th>");
$("#listTable").append(tr);
tr = $("<tr></tr>");
tr.append("<td colspan=5>게시글 정보가 존재하지 않습니다.</td>");
$("#listTable").append(tr);
paging(0);
}
})
5. 컨트롤러는 이에 따라 게시글 목록을 클라이언트에게 돌려준다.
여기까지의 과정이 게시글 목록을 불러오는 절차를 간단하게 나타낸 것이다.
그렇다면 본격적으로 게시글 읽기 과정이 어떤 식으로 진행되는지 알아보자.
1. 컨트롤러에 viewArticle.do 요청을 한다.
이때 파라미터로 게시글이 속한 게시판 종류와 게시글 번호를 파라미터로 같이 전달한다.
컨트롤러는 서비스 객체의 viewArticle( ) 메서드를 호출한다.
@RequestMapping(value="/board/viewArticle.do")
public ModelAndView viewArticle(HttpServletRequest request, HttpServletResponse response){
try {
String BOARD = request.getParameter("BOARD");
int ARTICLE_ID = Integer.parseInt(request.getParameter("ARTICLE_ID"));
if(BOARD==null||(!BOARD.equals("free_board")&&!BOARD.equals("qna_board")&&!BOARD.equals("info_board"))) {
return new ModelAndView("redirect:/customer/mainForm.do");
}
HashMap map = new HashMap();
map.put("BOARD", BOARD);
map.put("ARTICLE_ID", ARTICLE_ID);
return boardService.viewArticle(map);
}catch(Exception e) {
return new ModelAndView("redirect:/customer/mainForm.do");
}
}
2. 서비스 객체는 DAO 객체의 viewArticle( ) 메서드를 호출하여 게시글의 제목, 내용, 게시글 번호 등등을 읽어온다.
만약 성공적으로 게시글을 읽어왔다면 updateViews( ) 메서드를 호출해 해당 게시글의 조회수를 1만큼 증가시킨다.
또한 getFiles( ) 메소드를 호출해 첨부된 이미지 파일들의 이름들을 얻어온다.
그렇게 얻은 정보들을 모델 뷰 객체에 추가한다.
3. viewArticle.jsp 에서는 JSTL을 이용해 해당 게시글의 제목, 내용, 이미지들을 동적으로 출력한다.
<%@ 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" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<c:set var="path" value="${pageContext.request.contextPath}"/>
<script src="${path}/resources/js/jquery.js"></script>
<script src="${path}/resources/js/jquery-ui.js"></script>
<script src="${path}/resources/js/jquery-cookie.js"></script>
<script src="${path}/resources/js/JSEncrypt.js"></script>
<script src="${path}/resources/js/viewArticle.js"></script>
<link rel="stylesheet" href="${path}/resources/css/viewArticle.css">
<title>글 읽기</title>
<c:set var="articleVO" value="${requestScope.articleVO}"/>
</head>
<body>
<jsp:include page="/WEB-INF/views/sidebar.jsp"/>
<div id="container" style="overflow: scroll;" class="t3">
<c:choose>
<c:when test="${not empty articleVO}">
<form id="viewArticle" action="/LibraryService/board/modArticle.do?BOARD=${BOARD}" method="post" enctype="multipart/form-data">
<input type="hidden" name="ORIGINAL_ARTICLE_ID" value="${articleVO.ARTICLE_ID}">
<input id="ARTICLE_ID" type="hidden" name="ARTICLE_ID" value="${articleVO.ARTICLE_ID}">
<input type="hidden" name="CUSTOMER_ID" value="${articleVO.CUSTOMER_ID}">
<input id="BOARD" type="hidden" name="BOARD" value="${BOARD}">
<input id="CUSTOMER_ID" type="text" value="${articleVO.CUSTOMER_ID}" disabled>
<p id="ARTICLE_DATE">${articleVO.ARTICLE_DATE_STRING}</p>
<p class="subtitle">제목</p>
<input id="ARTICLE_TITLE" class="v input" type="text" name="ARTICLE_TITLE" value="${articleVO.ARTICLE_TITLE}" disabled>
<p class="subtitle">내용</p>
<textarea id="ARTICLE_CONTENT" class="v input" name="ARTICLE_CONTENT" disabled>${articleVO.ARTICLE_CONTENT}</textarea>
<div id="FILE">
<c:if test="${not empty FILES}">
<c:forEach items="${FILES}" var="file" varStatus="status">
<div class="fileList">
<img name='file${status.index}' class="image" src="${path}/board/download.do?BOARD=${BOARD}&ARTICLE_ID=${file.ARTICLE_ID}&FILE_TEMP_NAME=${file.FILE_TEMP_NAME}">
<input id="file${status.index}" class="v" type="file" name="file${status.index}" onchange="changeFile(this); preview(this);" style="display:none;"disabled>
<input class="FILE_TEMP_NAME" type="hidden" name="FILE_TEMP_NAME" value="${file.FILE_TEMP_NAME}">
<label class='label' for="file${status.index}"></label>
<input type="button" value="X" class="v remove" onclick="removeFile(this);"disabled>
</div>
</c:forEach>
</c:if>
</div>
<div id="control1">
<input id="return" type="button" value="목록보기">
<input id="reply" type="button" value="답글쓰기">
<c:if test="${(not empty sessionScope.CUSTOMER)&&sessionScope.CUSTOMER.CUSTOMER_ID == articleVO.CUSTOMER_ID}">
<input id="revise" type="button" value="수정하기">
<input id="remove" type="button" value="삭제하기">
</c:if>
</div>
<div id="control2" style="display:none;">
<input id="FILE_ADD_BUTTON" type="button" value="파일추가" style="display:none">
<input id="confirm" type="button" value="완료하기">
<input id="cancel" type="button" value="취소하기">
</div>
</form>
</c:when>
<c:otherwise>
<p id="error">해당 게시글 정보가 존재하지 않습니다.</p>
</c:otherwise>
</c:choose>
</div>
</body>
</html>
4. 첨부된 이미지를 출력할 때는 컨트롤러에 download.do 요청을 한다.
이때 파라미터로 게시판의 종류, 게시글 번호, DB에 저장된 이미지의 임시 이름을 전달한다.
이미지의 원래 이름이 아닌 임시 이름을 전달하는 이유는, 행여나 서로 다른 디렉터리에 존재하는
같은 이름의 이미지를 업로드했을 때의 중복을 해결하기 위해 무작위 이름으로 DB에 저장했기 때문이다.
5. 컨트롤러에 download.do 요청을 했을 때 해당 이미지 파일이 서버에 존재한다면
이 이미지 파일의 입력 스트림을 얻어서 이미지 파일을 읽고, 아까 얻은 출력 스트림 객체를 통해 출력한다.
@RequestMapping(value="/board/download.do")
public void downloadFile(HttpServletRequest request, HttpServletResponse response){
try {
String BOARD = request.getParameter("BOARD");
String FILE_TEMP_NAME = request.getParameter("FILE_TEMP_NAME");
int ARTICLE_ID = Integer.parseInt(request.getParameter("ARTICLE_ID"));
if(BOARD==null||(!BOARD.equals("free_board")&&!BOARD.equals("qna_board")&&!BOARD.equals("info_board"))||FILE_TEMP_NAME==null) {
return;
}
File file = new File(REPOSITORY_PATH+"\\"+BOARD+"\\"+ARTICLE_ID+"\\"+FILE_TEMP_NAME);
if(file.exists()) {
response.setHeader("Cache-Control", "no-cache");
response.addHeader("Content-disposition", "attachment; fileName="+FILE_TEMP_NAME);
OutputStream out = response.getOutputStream();
//Thumbnails.of(file).size(300, 300).outputFormat("png").toOutputStream(out);
byte[] buffer = new byte[1024*1024*10];
//out.write(buffer);
FileInputStream in = new FileInputStream(file);
while(true) {
int cnt = in.read(buffer);
if(cnt== -1) {
break;
}else {
out.write(buffer,0,cnt);
}
}
out.close();
in.close();
}
}catch(Exception e) {
e.printStackTrace();
}
}
'Web Service > 도서 정보 제공 웹 서비스' 카테고리의 다른 글
도서 정보 제공 웹 서비스 - 게시글 수정 (0) | 2022.02.25 |
---|---|
도서 정보 제공 웹 서비스 - 게시글 삭제 (0) | 2022.02.07 |
도서 정보 제공 웹 서비스 - 게시글 쓰기 (0) | 2022.01.20 |
도서 정보 제공 웹 서비스 - Gmail을 활용한 비밀번호 찾기 (0) | 2022.01.09 |
도서 정보 제공 웹 서비스 - 메세지 읽기, Spring, Mybatis 프레임워크 (0) | 2022.01.06 |