관리자 글쓰기

본 내용은 혼자 공부하기 위하여 다른 포스트를 보면서 저에게 필요한 부분과 궁금했던 부분을 추가하여 게시하는 곳입니다.

궁금한 것에 대한 것은 모르는 것이 많겠지만 함께 알아보도록 노력하겠습니다.


참조 게시 포스트 : http://addio3305.tistory.com/


------------------------------------------------------------------------------------------------------------------------------------------

1. 파일 다중 업로드

1.1 JSP

파일 다중 업로드를 위해 jsp 파일을 수정하겠다.

boardWrite.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<title>글쓰기</title>
<%@ include file="/WEB-INF/include/include-header.jspf" %>
</head>
<body>
	<form id="frm" enctype="multipart/form-data" >
		<table class="board_view">
			<colgroup>
				<col width="15%" >
				<col width="*" >
			</colgroup>
			<caption>게시글 작성</caption>
			<tbody>
				<tr>
					<th scope="row">제목</th>
					<td><input type="text"  name="TITLE" class="wdp_90" /></td>
				</tr>
				<tr>
					<th scope="row">내용</th>
					<td><textarea cols="100" rows="20" id="CONTENTS" name="CONTENTS" title="내용"></textarea></td>
				</tr>
			</tbody>
		</table>
		<div id="fileDiv">
			<p>
				<input type="file" name="file_0"/>
				<a href="#this" name="delete" class="btn">삭제하기</a>
			</p> 
		</div>
		<br/><br/>
		<a href="#this" id="add" class="btn">파일 추가하기</a>
		<a href="#this" id="list" class="btn">목록으로</a>
		<a href="#this" id="write" class="btn">글쓰기</a>
	</form>
	<%@ include file="/WEB-INF/include/include-body.jspf" %>	
	
	<script type="text/javascript">
		var g_count =1;
		$(document).ready(function(){
			$("#list").on("click",function(e){
				e.preventDefault();
				fn_openBoardList();
			})
			$("#write").on("click",function(e){
				e.preventDefault();
				fn_writeBoard();
			})
			$("a[name='delete']").on("click",function(e){
				e.preventDefault();
				fn_fileDelete($(this));
			})
			$("#add").on("click",function(e){
				e.preventDefault();
				fn_fileAdd();
			})
		});
		
		
		function fn_openBoardList(){
			var comSubmit = new ComSubmit();
			comSubmit.setUrl("<c:url value='/sample/openBoardList.do'/>");
			comSubmit.submit();
		}
		
		function fn_writeBoard(){
			var comSubmit = new ComSubmit("frm");
			comSubmit.setUrl("<c:url value='/sample/writeBoard.do'/>");
			comSubmit.submit();
		}
		function fn_fileDelete(obj){
			obj.parent().remove();
		}
		function fn_fileAdd(){
			var str = "<p><input type='file' name='file_"+(g_count++)+"'/><a href='#this' name='delete' class='btn'>삭제하기</a></p> ";
			$("#fileDiv").append(str);
			
			$("a[name='delete']").on("click",function(e){
				e.preventDefault();
				fn_fileDelete($(this));			
			})
		}
	</script>
</body>
</html>

- 31 줄

여기서 name이 file_0로 한 이유가 설명이 된다. 다른 파일들과 name에서 뒤의 숫자로 각각을 판단하여 추가하거나 삭제할 수 있도록 하기 위하여 name을 설정해주었다.


- 32,36 줄

삭제하기와 파일 추가하기 버튼을 생성해주었다. 이 버튼들로 더 등록할 파일을 추가하거나 삭제할 수 있다.


-43 줄

g_count 라는 것이 파일들을 더 추가할 때 서로 구분하기 위하여 name 뒤에 숫자를 붙일 때 쓰는 변수이다.


- 53~60 줄

삭제하기와 파일 추가하기 버튼에 대한 이벤트 처리를 주었다.


- 75~77 줄

삭제하기 버튼의 처리 중 해당 버튼의 부모 클래스를 지움으로써 파일 부분을 지우는데 여기서 부모 클래스는 fileDiv 안에 포함되어 있는 <p> 태그이다. 따라서 <p>태그를 지우면서 <input type="file"> 도 함께 지워진다.


- 78~85 줄

파일 추가하기 버튼의 처리로 <p>태그로 묶은 부분을 전체적으로 다 변수에 넣고 <div>태그에 추가하여 준다. 또한 해당 삭제하기 버튼에도 따로 이벤트 처리를 줌으로써 각각 고유의 삭제하기 버튼의 처리 기능이 완성 된다.


여기까지하면 전에 파일 업로드를 할 때 리스트와 반복물을 통해 파일들에 대한 처리를 서비스단이나 FileUtils에서 구성해주었기 때문에 문제없이 될 것이다. 상세보기에서도 첨부파일 부분은 반복문을 써놓았기 때문에 따로 수정할 필요는 없다.


1.2 테스트

다음과 같이 작성하고 파일을 2개 추가하였다.


- 다음 화면은 그 결과로 상세보기 화면에서도 2개의 파일을 볼 수 있다.



- 다음은 로그 화면으로 2개의 insertFile의 DAO 로그와 SQL 로그를 볼 수 있다.




2. 게시판 수정하기(파일 업로드)

앞서 게시판 수정하기에서는 글 제목과 글 내용만 수정하였다. 이제는 파일 업로드라는 새로운 것이 추가 되었기에 새로 코드를 수정하여야 한다. 하지만 수정하기에 앞서 어떤 식으로 만들 지에 대해 고민해 보아야 한다.


2.1 게시판 수정하기 처리

- 게시글의 내용만 수정하고 파일은 그대로한다.

- 기존의 파일을 변경한다.

- 기존의 파일을 삭제한다.

- 기존의 파일을 두고 새로운 파일을 추가한다.

- 기존의 파일을 삭제하고 파일을 추가한다.

- 기존의 파일을 수정하고 파일을 추가한다.

... 등등 여러가지 처리에 대한 방법이 필요할 것이다.


모두 같은 방법을 쓰는 것은 아니고 여기에서는 해당 게시물에 포함되는 모든 파일들을 삭제처리(DEL_GB="Y")를 하고 기존의 파일이 변경되면 새롭게 추가하고 파일이 추가되면 또 새롭게 추가하고 삭제했을 경우 그대로 두고 그대로 두었을 땐 삭제처리만 번복(DEL_GB="N")을 해주는 방식을 사용할 것이다. 그렇게 하기 위하여 먼저 Controller 부분을 작성하자.


2.2 Controller

SampleController.java

	@RequestMapping(value="/sample/openBoardModify.do")
	public ModelAndView openBoardModify(CommandMap commandMap) throws Exception{
		ModelAndView mv = new ModelAndView("/sample/boardModify");
		Map<String,Object> map = sampleService.selectBoard(commandMap.getMap());
		mv.addObject("map",map.get("map"));
		mv.addObject("list",map.get("list"));
		return mv;
	}
	
	@RequestMapping(value="/sample/modifyBoard.do")
	public ModelAndView modifyBoard(CommandMap commandMap, HttpServletRequest req) throws Exception{
		ModelAndView mv = new ModelAndView("redirect:/sample/openBoardDetail.do");
		sampleService.modifyBoard(commandMap.getMap(),req);
		
		mv.addObject("IDX", commandMap.get("IDX"));
		return mv;
	}

- openBoardModify를 수정하자. Detail과 동일하게 파일을 넘겨줄 수 있게 list를 따로 할당해주었다. 크게 변경한 것은 없다. 또한 modifyBoard에도 파라미터로 HttpServletRequest 를 추가하였고 Service 단에도 같이 넘겨주었다.


2.3 Service

SampleServiceImpl.java

	@Override
	public void modifyBoard(Map<String, Object> map, HttpServletRequest req) throws Exception {
		sampleDAO.modifyBoard(map);
		
		sampleDAO.deleteFile(map,req);
		
		List<Map<String, Object>> list = fileUtils.parseUpdateFileInfo(map, req);
		Map<String,Object> tempMap = null;
		for(int i = 0; i<list.size();i++) {
			tempMap = list.get(i);
			
			if(tempMap.get("IS_NEW").equals("Y")) {
				sampleDAO.insertFile(tempMap);
			}else {
				sampleDAO.updateFile(tempMap);
			}
		}
	}

- modifyBoard를 수정하자. HttpServletRequest 파라미터를 추가하였다.


- 5 줄

deleteFile 이라는 것은 아까 위에서 말했듯이 해당 게시물에 포함되는 해당 파일들을 삭제하는 부분이다. (DEL_GB="Y")


- 7 줄

우리가 만든 fileUtils 클래스의 메서드를 쓰는 것으로 parseInserFileInfo와 비슷한 기능을 하는 것으로 파일 정보를 list에 반환한다.


- 9~17 줄

반환 받은 리스트들, 즉 파일들에 IS_NEW라는 키가 있다. 해당 키는 새롭게 추가된 파일인지 기존의 파일인지를 구분하기 위해 만든 키로 Y는 새로운 파일로 insert 처리를 하고, N은 기존의 파일로 하여 update 처리를 할 수 있게 해주었다.


2.4 DAO

SampleDAO.java

	public void deleteFile(Map<String, Object> map, HttpServletRequest req) {
		update("sample.deleteFile",map);
	}

	public void updateFile(Map<String, Object> map) {
		update("sample.updateFile",map);
	}

- deleteFile과 updateFile 에 대한 sql 처리를 담당하는 메서드들을 추가하였다. Service 단에서 사용했던 것들로 insertFile은 기존에 이미 있다.


2.5 SQL

sample_SQL.xml

     <update id="sample.deleteFile" parameterType="hashmap">
     	<![CDATA[
     		UPDATE TB_FILE1
     		SET	DEL_GB = 'Y'
     		WHERE
     			BOARD_IDX=#{IDX}
     	]]>
     </update>
     
     <update id="sample.updateFile" parameterType="hashmap">
     	<![CDATA[
     		UPDATE TB_FILE1
     		SET DEL_GB = 'N'
     		WHERE
     			IDX=#{FILE_IDX}
     	]]>
     </update>

- deleteFile에서 IDX는 게시글의 번호로 게시글에 해당 되는 모든 파일을 지우기 위하기 위함이고, updateFile에서 IDX는 파일 번호로 기존에 있던 파일 중 변경되지 않은 파일을 다시 복구하는 것이다. 물론 삭제는 DELETE 하는 것이 아니라 DEL_GB를 사용하여 기존에 보이지 않도록만 하는 것이다. 


2.6 FileUtils

FileUtils.java

public List<Map<String, Object>> parseUpdateFileInfo(Map<String, Object> map, HttpServletRequest req) throws Exception {
		MultipartHttpServletRequest mulReq = (MultipartHttpServletRequest)req;
		MultipartFile mulFile=null;
		
		String original_File_Name = null;
		String stored_File_Name = null;
		String original_Extension = null;
		
		Iterator<String> iterator = mulReq.getFileNames();
		String board_IDX = map.get("IDX").toString();
		String IDX=null;
		List<Map<String,Object>> fileList = new ArrayList<Map<String,Object>>();
		Map<String,Object> fileMap = null;
		while(iterator.hasNext()) {
			mulFile = mulReq.getFile(iterator.next());
			
			if(mulFile.isEmpty()==false) {
				original_File_Name = mulFile.getOriginalFilename();
				original_Extension = original_File_Name.substring(original_File_Name.lastIndexOf("."));
				stored_File_Name = CommonUtils.getRandomString() + original_Extension;
				
				mulFile.transferTo(new File(filePath+stored_File_Name));
				
				fileMap = new HashMap<String,Object>();
				fileMap.put("BOARD_IDX", board_IDX);
				fileMap.put("ORIGINAL_FILE_NAME", original_File_Name);
				fileMap.put("STORED_FILE_NAME", stored_File_Name);
				fileMap.put("FILE_SIZE", mulFile.getSize());
				fileMap.put("IS_NEW","Y");
				fileList.add(fileMap);
				
			}else {
				String requestName = mulFile.getName();
				IDX = "IDX_"+requestName.substring(requestName.indexOf("_")+1);
				if(map.containsKey(IDX) == true &&map.get(IDX)!=null) {
					fileMap = new HashMap<String,Object>();
					fileMap.put("IS_NEW", "N");
					fileMap.put("FILE_IDX",map.get(IDX));
					fileList.add(fileMap);
				}
			}
		}
		return fileList;
	}

- 전에 작성했던 parseInsertFileInfo와 매우 흡사한 구조이다. 특별히 다른 것이라고는 파일 번호의 IDX가 필요하여 변수로 하여 받아온다는 것과 14~42줄 부분이다.


- 14~42줄

기존과 다르게 fileMap에 IS_NEW 키를 넣는 것을 알 수 있다. 위에서 말했듯이 새로운 파일이냐 아니냐를 구분하는 키이다. 기존의 파일인지 아닌지는 17줄의 if(mulFile.isEmpty()==false)를 통해서 알 수 있는데 수정하기 페이지에서 기존의 파일을 그대로 둘 시에는 Request에 포함되지 않는데 이는 <input type="file"> 태그로 넘겨져 오는데 이 부분이 없기 때문이다. 따라서 기존의 파일과 새 파일들은 17줄로 구분을 할 수 있다. 그러면 어떤 식으로 기존 파일 중에서도 삭제된 파일이나 수정된 파일 그리고 그대로 둔 파일을 구분 짓는 지에 대해서는 33~35줄을 보면 된다. mulFile로 부터 Name을 받아오는데 이는 <input type="file" > 태그의 name 속성에 해당하는 부분을 가져온다. 

예시로 작성을 해보자면 <input type="file" name="file_0"> 이라면 file_0을 가져오는 것이다. requestName에는 file_0이 있는 상태에서 34줄이 IDX = "IDX_" + "0" 처럼 해석이 되는 것이다. IDX = IDX_0이 되어 map.containsKey(IDX)를 통해 수정하기 페이지 중에 name이 IDX_0이 있는 것과 값을 갖고 있는 지 확인 뒤 그 값을 fileMap에 추가하는 것을 볼 수 있다. 물론 name이 IDX_0인 태그의 값은 기존 파일의 IDX 값을 갖고 있다.


2.7 JSP

boardModify.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>글 수정하기</title>
<%@ include file="/WEB-INF/include/include-header.jspf" %>
</head>
<body>
	<form id="frm" enctype="multipart/form-data">
		<table class="board_view">
			<caption>글 수정하기</caption>
			<colgroup>
				<col width="15%">
				<col width="35%">
				<col width="15%">
				<col width="35%">
			</colgroup>
			<tbody>
                <tr>
                    <th scope="row">글 번호</th>
                    <td>
                        ${map.IDX }
                        <input type="hidden" id="IDX" name="IDX" value="${map.IDX }">
                    </td>
                    <th scope="row">조회수</th>
                    <td>${map.HIT_CNT }</td>
                </tr>
                <tr>
                    <th scope="row">작성자</th>
                    <td>${map.CREA_ID }</td>
                    <th scope="row">작성시간</th>
                    <td>${map.CREA_DTM }</td>
                </tr>
                <tr>
                    <th scope="row">제목</th>
                    <td colspan="3">
                        <input type="text" id="TITLE" name="TITLE" class="wdp_90" value="${map.TITLE }"/>
                    </td>
                </tr>
                <tr>
                	<th scope="row">내용</th>
                    <td colspan="3" class="view_text">
                        <textarea rows="20" cols="100" title="내용" id="CONTENTS" name="CONTENTS">${map.CONTENTS }</textarea>
                    </td>
                </tr>
                <tr>
                	<th>첨부파일</th>
                	<td colspan="3">
                		<div id="fileDiv">
                			<c:forEach items="${list }" varStatus="row" var="var" >
                				<p>
                					<input name="IDX_${row.index }" id="IDX" type="hidden" value="${var.IDX }"/>
                					<a href="#this" name="name_${row.index }" id="name_${row.index }">${var.ORIGINAL_FILE_NAME }</a>
                					<input type="file" name="file_${row.index }">
                					<a href="#this" id="delete_${row.index }" name="delete_${row.index }" class="btn">삭제하기</a>
                				</p>
                			</c:forEach>
                		</div>
                	</td>	
				</tr>
            </tbody>
		</table>
	</form>
	<a href="#this" id="list" class="btn">목록으로</a>
	<a href="#this" id="add" class="btn">파일 추가하기</a>
	<a href="#this" id="modify" class="btn">수정하기</a>
	<a href="#this" id="delete" class="btn">삭제하기</a>
	
	<%@ include file="/WEB-INF/include/include-body.jspf" %>
	<script type="text/javascript">
		var gfv_count = '${fn:length(list)+1}';
		$(document).ready(function(){
			$("#modify").on("click",function(e){
				e.preventDefault();
				fn_modifyBoard();
			})
			$("#list").on("click",function(e){
				e.preventDefault();
				fn_openBoardList();
			})
			$("#delete").on("click",function(e){
				e.preventDefault();
				fn_deleteBoard();
			})
			$("#add").on("click",function(e){
				e.preventDefault();
				fn_fileAdd();	
			})
			$("a[name^='delete']").on("click",function(e){
				e.preventDefault();
				fn_fileDelete($(this));
			})
		})
        function fn_fileAdd(){
            var str = "<p>" +
                    "<input type='file' id='file_"+(gfv_count)+"' name='file_"+(gfv_count)+"'>"+
                    "<a href='#this' class='btn' id='delete_"+(gfv_count)+"' name='delete_"+(gfv_count)+"'>삭제</a>" +
                "</p>";
            $("#fileDiv").append(str);
            $("#delete_"+(gfv_count++)).on("click", function(e){ //삭제 버튼
                e.preventDefault();
                fn_fileDelete($(this));
            });
        }
		function fn_fileDelete(obj){
			obj.parent().remove();
		}
		
		function fn_modifyBoard(){
			var comSubmit = new ComSubmit("frm");
			comSubmit.setUrl("<c:url value='/sample/modifyBoard.do'/>");
			comSubmit.submit();
		}
		function fn_openBoardList(){
			var comSubmit = new ComSubmit();
			comSubmit.setUrl("<c:url value='/sample/openBoardList.do'/>");
			comSubmit.submit();
		}
		function fn_deleteBoard(){
			var comSubmit = new ComSubmit("frm");
			comSubmit.setUrl("<c:url value='/sample/deleteBoard.do'/>");
			comSubmit.submit();
		}
	</script>
</body>
</html>

- 52~57 줄

해당 부분이 기존 파일이 있고 파일을 추가하거나 변경하고 삭제하고 할 수 있는 부분이다. 위에서 말했던 기존 파일에서 IDX 값을 가져오는 부분으로 55줄을 보면 <input type="file" name="file_${row.index}"> 라고 되있는 부분이 있다. 이 부분의 name값이 위에서 말했던 것처럼 돌아가는 것이다. row.index라는 것은 forEach 구문에서 속성 varStatus로 지정된 변수의 index값으로 0부터 증가하여 고유의 번호들을 지정해준다.


- 72 줄

var gfv_count 가 위에서 index값과 동일한 기능을 한다. 파일들의 개수를 계산하여 +1하여 중복을 피하였다.


- 95~105 줄

글쓰기 부분에서 추가하는 부분과 비슷한 방식이지만 고유 번호를 위한 gfv_count의 빈도 수가 많다보니 조금은 복잡해 보인다.


후에 다른 것은 크게 주목할 것들은 없다. 이후 테스트를 해보자.


3. 테스트

3.1 파일을 수정



- 위에 파일 하나만 수정하고 수정하여 보았다.



- 정상적으로 잘 처리 된다.



- 로그의 경우 modifyBoard로 게시물의 제목과 내용을 수정하고 deleteFile로 해당 파일들을 전부 삭제 한 뒤, insertFile로 수정한 파일을 새로 추가하고 기존에 그대로 남아 있는 파일을 복구 시키는 updateFile을 볼수 있다.


3.2 파일을 모두 삭제

- 파일을 모두 삭제하고 수정해보았다.



- 모두 삭제된 결과를 볼 수 있다.



- modifyBoard와 deleteFile 처리만 일어난 것을 볼 수 있다.


3.3 파일이 없는 게시물에 파일 추가

- 파일이 비어있던 게시물에 파일을 2개 추가하여 수정하여 보았다.




- 정상적으로 완료된 것을 볼 수 있다.



- modifyBoard가 실행되고 아무것도 없지만 deleteFile도 실행된 뒤 insertFile이 2개가 실행됨을 볼 수 있다. deleteFile에 대한 것은 스스로 조건을 달아 파일이 없는 경우에는 실행되지 않도록 만들어보도록 하고 여기까지 따라왔으면 잘 따라왔다는 것이다.