일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- html5
- 풀스택
- 오라클
- css3
- Ajax
- 네비게이터
- mybatis
- 제이쿼리
- 로그인
- 서블릿
- spring
- dbms
- 스프링
- 프론트엔드
- jsp
- Linked List
- 웹개발
- 웹페이지
- c programming
- 비밀번호찾기
- MVC
- 웹서비스
- jQuery
- 마이바티스
- Binding
- 백엔드
- 미로 생성 알고리즘
- 회원가입
- 프레임워크
- javascript
- Today
- Total
Programmer's Progress
도서 정보 제공 웹 서비스 - 게시글 쓰기 본문
앞서 구현한 메시지의 경우는 그 동작이 무척이나 간단했다.
그저 textarea 태그에 작성한 메시지 내용이나, input 태그에 입력한 제목을
그저 송신, 수신함 릴레이션에 데이터를 insert 했을 뿐이었다.
그런데 이번에 구현하는 게시판의 경우는 좀 얘기가 다르다.
단순히 게시글 내용만 DB에 저장하는 것이 아니다.
이미지 파일 또한 서버에 저장해야 할 것이다.

자유게시판, 문의게시판, 정보 게시판의 스키마 다이어그램

하나의 게시글은 여러 개의 파일을 첨부하여 작성할 수 있지만
하나의 파일은 하나의 게시글에만 첨부될 수 있으므로 1:N의 관계를 갖는다.
따라서 ER 모델링을 R모델링으로 변환할 때 1 관계에 속하는 게시글의 기본키인 ARTICLE_ID를 포함하여
임시 파일 이름을 기본키로 삼도록 스키마를 설계한다.
addArticle.js의 소스코드
var CNT,MAX=12;
function checkBytes(text, MAX_BYTES){
var sum = 0;
for(var i=0;i<text.length;i++){
if(escape(text.charAt(i)).length>4){
sum=sum+3;
}else{
sum++;
}
}
if(sum>MAX_BYTES){
return false;
}else{
return true;
}
}
function preview(){
var id = $(this).attr("id");
if(this.files&&this.files[0]){
var reader = new FileReader();
reader.onload = function(e){
$("img[name='"+id+"']").attr({
"src":e.target.result
});
}
reader.readAsDataURL(this.files[0]);
}
}
$(document).ready(function(){
var index = 1;
CNT = $("input[type='file']").length;
$(document).on("click","#addArticle",function(){
if($("#ARTICLE_TITLE").val().length == 0){
alert("게시글 제목을 입력해야합니다.");
}else if($("#ARTICLE_CONTENT").val().length == 0){
alert("게시글 내용을 입력해야합니다.");
}else if(!checkBytes($("#ARTICLE_TITLE").val(), 300)){
alert("게시글 제목은 한글 100자, 영어 및 숫자 300자로 구성되어야 합니다.");
}else if(!checkBytes($("#ARTICLE_CONTENT").val(), 3000)){
alert("게시글 내용은 한글 1000자, 영어 및 숫자 3000자로 구성되어야 합니다.");
}else{
var formData = new FormData();
formData.append("ARTICLE_TITLE",$("#ARTICLE_TITLE").val());
formData.append("ARTICLE_CONTENT",$("#ARTICLE_CONTENT").val());
formData.append("BOARD",$("#BOARD").val());
formData.append("PARENT_ARTICLE_ID",$("#PARENT_ARTICLE_ID").val());
var length = $("input[name^='file']").length;
for(var i=0; i<length;i++){
formData.append("file"+i, $("input[name^='file']")[i].files[0]);
}
$.ajax({
"url":"/LibraryService/board/addArticle.do",
"type":"POST",
"data":formData,
processData: false,
contentType: false,
"success":function(result){
alert(result.CONTENT);
var form = $("<form action='/LibraryService/board/boardForm.do' method='post'></form>");
form.append("<input type='text' name='BOARD' value='"+$("#BOARD").val()+"'/>");
$("body").append(form);
form.submit();
},
"error":function(){
alert("에러");
}
})
}
});
$(document).on("click","#return",function(){
var form = $("<form></form>");
form.attr({
"action":"/LibraryService/board/boardForm.do",
"method":"post"
});
form.append("<input name='BOARD' value='"+$("#BOARD").val()+"'/>");
$("body").append(form);
form.submit();
})
$(document).on("click","#FILE_ADD_BUTTON",function(){
if(CNT<MAX){
CNT++;
var div = $("<div id='fileList"+index+"' class='fileList'></div>");
div.append("<label class='label' for='file"+index+"'></label>");
div.append("<input id='file"+index+"' class='file' name='file"+index+"'type='file'>");
div.append("<img class='image' name='file"+index+"'/>");
$(document).on("change","#file"+index,preview);
var remove = $("<a class='remove' id='remove"+index+"'>X</a>");
$(document).on("click","#remove"+index,function(){
$(this).parent().off();
$(this).parent().remove();
cnt--;
});
div.append(remove);
index++;
$("#FILE").append(div);
}else{
alert("최대 "+MAX+"개의 파일을 업로드할 수 있습니다.");
}
})
});
AJAX로 게시글 작성을 요청할 때 FormData 객체를 전달하는 것에 유의하자.
컨트롤러가 HttpServletRequest가 아닌 MultipartHttpServletRequest 요청을 기대하고 있기 때문에
단순히 data를 전달해서는 안 되고, 반드시 FormData 객체를 전달해야만 제대로 요청이 수행되었다.
FormData가 아니라 바로 data를 전달하는 경우, 해당 요청이 Multipart 한 요청이 아닌 text로 인식이 되었다.
게시글 작성에 관한 자바 소스코드를 확인해보자.
아래의 소스코드는 BoardController.java의 소스코드다.
@RequestMapping(value="/board/addArticle.do")
@ResponseBody
public HashMap LOGONMAP_addArticle(MultipartHttpServletRequest request, HttpServletResponse response){
HashMap result = new HashMap();
try {
String BOARD = request.getParameter("BOARD");
if(BOARD==null||(!BOARD.equals("free_board")&&!BOARD.equals("qna_board")&&!BOARD.equals("info_board"))) {
result.put("FLAG", "FALSE");
result.put("FALSE", "게시글 작성에 실패했습니다.");
return result;
}
CustomerVO customerVO = (CustomerVO) request.getSession().getAttribute("CUSTOMER");
HashMap map = new HashMap();
map.put("CUSTOMER_ID", customerVO.getCUSTOMER_ID());
map.put("ARTICLE_TITLE", request.getParameter("ARTICLE_TITLE"));
map.put("ARTICLE_CONTENT", request.getParameter("ARTICLE_CONTENT"));
map.put("PARENT_ARTICLE_ID", Integer.parseInt(request.getParameter("PARENT_ARTICLE_ID")));
map.put("ARTICLE_VIEWS", 0);
map.put("BOARD", BOARD);
result = boardService.addArticle(map);
int ARTICLE_ID = (Integer) result.get("ARTICLE_ID");
Iterator<String> iterator = request.getFileNames();
List<HashMap> fileNames = new ArrayList<HashMap>();
while(iterator.hasNext()) {
String fileName = iterator.next();
MultipartFile mFile = request.getFile(fileName);
String originalFileName = mFile.getOriginalFilename();
String tempFileName = SHA.DSHA512(SHA.getSalt(), SHA.getSalt()).substring(0, 32);
String fileExtension = mFile.getOriginalFilename().substring(mFile.getOriginalFilename().lastIndexOf("."));
File dest = new File(REPOSITORY_PATH+"\\"+BOARD+"\\"+ARTICLE_ID+"\\"+tempFileName+fileExtension);
HashMap info = new HashMap();
info.put("tempFileName",tempFileName+fileExtension);
info.put("originalFileName",originalFileName);
fileNames.add(info);
dest.mkdirs();
mFile.transferTo(dest);
}
if(fileNames.size()>0) {
HashMap param = new HashMap();
param.put("ARTICLE_ID", ARTICLE_ID);
param.put("fileNames", fileNames);
param.put("BOARD_FILE", BOARD+"_file");
boardService.addFiles(param);
}
return result;
}catch(Exception e) {
e.printStackTrace();
result.put("FLAG", "FALSE");
result.put("CONTENT", "게시글 작성에 실패했습니다.");
return result;
}
}
소스코드를 설명하기에 앞서, 스프링 프레임워크에서 제공하는 AOP기능을 통해
로그인 여부를 먼저 파악하고 정보가 유효하지 않다면 main 화면으로 이동하게끔 설정했음을 밝힌다.
이는, 다음 포스팅에서 자세하게 다루도록 하겠다.
HashMap의 인스턴스인 map는 마이 바티스 프레임워크에서 사용할 파라미터를 전달하는 인스턴스다.
당연하게도 게시글에는 작성자 ID, 제목, 내용, 부모 게시글 ID, 조회수 등등의 정보가 필요하다.
BOARD의 경우, 이것이 자유게시판의 글인지, 정보 게시판, 문의게시판의 글인지 구분하기 위한 값이다.
이는 사이드바 메뉴에서 /LibraryService/board/addArticleForm.do?BOARD=free_board와 같은 요청을 통해
전달되는 값이다. 만약 이 값이 유효하지 않다면, 어떤 게시판을 선택한 것인지 알 수 없으므로 main화면으로 이동한다.
MultipartHttpServletRequest의 경우 클라이언트가 JS의 AJAX를 통하여 input [type=file]의 값들을 전달한다.
그 후에 request.getFileNames( ) 메서드를 통해 반복자를 얻어온다.
반복자에는 input의 name들이 저장되어 있는데, 이는 게시글 작성자가 파일을 하나만 전송하는 것이 아니고
자신이 원하는 만큼 파일을 전송하기 위해서 각 input의 name을 서로 중복되지 않도록 file1, file2와 같이
1씩 증가시키는 방향으로 name을 설정하였고, 그 와중에 업로드를 취소했다가 다시 파일을 업로드하면
file1, file2, file6, file7과 같이 불연속적인 name을 가질 수도 있기 때문이었다.
이 파일을 서버에 업로드할 때 발생할 수 있는 문제가 있다.
바로, 원본 파일의 이름이 중복되는 경우다.
예를 들어 폴더 A, B에 각각 고양이. jpg라는 파일이 존재할 때
이를 서버에 업로드하게 되는 경우 둘 다 같은 이름을 가졌기에 어느 하나는 덮어씌워지는 문제가 있었다.
이를 방지하기 위해서는 서버에 실제로 파일을 저장할 때는 무작위 이름을 부여받도록 해야 했다.
이를 직접 작성한 더블 SHA알고리즘과, 무작위 SALT값을 시드와 SALT로 사용하고
그 해싱 결과를 해당 문자열 32자리를 추출하여 사용하였다.
그리고는 해당 이미지 파일이, 지금 작성한 게시글에 속한다는 정보를 저장하기 위해서
DB에 원본 이미지 파일 이름과 임시 파일 이름, 게시글 번호를 리스트에 담는다.
board.xml 맵퍼의 소스코드 일부
<insert id="addArticle" parameterType="java.util.HashMap">
<![CDATA[
INSERT INTO ${BOARD}
VALUES(${ARTICLE_ID},${PARENT_ARTICLE_ID},#{CUSTOMER_ID},#{ARTICLE_TITLE},#{ARTICLE_CONTENT},SYSDATE,${ARTICLE_VIEWS})
]]>
</insert>
<insert id="addFiles" parameterType="java.util.HashMap">
<foreach collection="fileNames" item="item" open="INSERT ALL" close="SELECT * FROM DUAL" separator=" ">INTO ${BOARD_FILE} VALUES(${ARTICLE_ID},#{item.tempFileName},#{item.originalFileName})</foreach>
</insert>
addArticle은 실제 게시글의 정보를 저장하는 동작이고
addFiles는 해당 게시글에 첨부된 파일들의 정보를 저장하는 동작이다.
사실 이미 글쓰기, 답글달기, 게시글 수정, 삭제, 목록, 검색, 페이징 등등의 기능을 모두 구현하였으나
글의 내용이 너무 많아지는 관계로 분할하여 포스팅을 하고, 동작을 보이도록 하겠다.
'Web Service > 도서 정보 제공 웹 서비스' 카테고리의 다른 글
도서 정보 제공 웹 서비스 - 게시글 삭제 (0) | 2022.02.07 |
---|---|
도서 정보 제공 웹 서비스 - 게시글 읽기 (0) | 2022.02.03 |
도서 정보 제공 웹 서비스 - Gmail을 활용한 비밀번호 찾기 (0) | 2022.01.09 |
도서 정보 제공 웹 서비스 - 메세지 읽기, Spring, Mybatis 프레임워크 (0) | 2022.01.06 |
도서 정보 제공 웹 서비스 - 메세지 보관함 구현하기 (0) | 2021.12.25 |