이전에 썸머노트를 구현하는 방법을 알아보았다. 오늘은 썸머노트에서 이미지 업로드에 대해 알아보려고 한다. 만약 구현하기 전이라면 아래 글부터 확인하고 오자!
1. 이미지 업로드 구현 화면 비교
썸머노트 에디터에서 이미지를 업로드하면 해당 이미지가 base64로 인코딩 후 저장되는 방식이라 이미지 파일 관리가 어렵고, 코드의 길이도 엄청 길게 나타난다. 해당 내용은 아래 GIF 이미지를 보면 확인할 수 있다.
한 개의 이미지를 올렸을 뿐인데.. 저렇게나 길어지다니... 만약 DB랑 연동했다면 저상태 그대로 DB에 저장되는 것이다. 또한 이미지 파일은 서버에 별도로 저장되지 않는다. 생각만 해도 끔찍하다..😨
위와 같이 나오는 걸 아래와 같이 변경하고자 한다. 아래 화면은 프로젝트시 적용된 UI화면을 캡처한 화면이다. 동일한 이미지를 업로드했는데 코드를 보면 이미지의 URL이 다르다.
과연 위와 같이 하려면 어떻게 해야할까? 그럼 지금부터 알아보자!
2. 필요한 라이브러리 다운로드 하여 적용하기
2-1 cos.jar
JSP 에는 파일 업로드 기능이 기본적으로 포함되어 있지 않다. 따라서 외부 라이브러리는 다운로드하여서 적용시켜 주어야 한다. 아래 링크로 접속하여 압축 파일을 다운로드해준다. 압축을 해제하면 lib 폴더 안에 cos.jar 파일을 확인할 수 있다.
2-2 json-simple-x.x.x.jar
또 하나 필요한 라이브러리는 파일 업로드시 url 리턴을 json 객체로 해주기 위해 json-simple 라이브러리가 필요하다. 아래 링크로 접속하여 다운로드하면 된다.
두 개의 jar 파일을 현재 작업 중인 프로젝트에서 webapp > WEB-INF > lib 폴더에 복사하여 붙여 넣기 해준다. 아래 캡처 화면은 이클립스를 사용한 사진이다. 위와 같이 복사해주면 해당 라이브러리에서 제공해주는 객체가 사용이 가능하다.
3.썸머노트 콜백 함수와 ajax 활용하기
위에 라이브러리가 준비되었다면 이제 본격적으로 이미지 업로드를 구현해 볼 차례이다. 우선 입력화면을 구현하기 위한 jsp 작업이 필요하다. 화면 구현 코드는 이전에 해보았기 때문에 별도로 첨부하지 않았다. 다만 스크립트에서 다른 점이 있다면 썸머노트에서 제공해주는 callback 함수를 추가해 주었다.
$(function() {
//서머노트 구현
$("#content").summernote(
{
width:1200,
height : 500,
minHeight : null,
maxHeight : null,
tabsize : 2,
focus : true,
lang : "ko-KR",
toolbar : [
// [groupName, [list of button]]
['insert', ['picture']],
['fontsize', ['fontsize']],
['fontName', ['fontname']],]
['style', ['bold', 'italic', 'underline', 'strikethrough', 'clear']],
['color', ['forecolor', 'backcolor']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
['table', ['table']],
['insert', ['link', 'video']],
['view', ['fullscreen', 'codeview', 'help']],
],
fontNames : [ '맑은 고딕', '궁서', '굴림체', '굴림', '돋움체', '바탕체'],
fontSizes : [ '8', '9', '10', '11', '12', '14', '16', '18',
'20', '22', '24', '28', '30', '36' ],
//이미지 업로드를 위해 콜백함수 추가
callbacks: {
onImageUpload : function(files, editor, welEditable){
sendFile(files[0], this);
}
}
});
});
썸머노트에서 제공해주는 onImageUpload 함수를 사용하여 에디터에 업로드되는 이미지를 서버로 전송해주는 함수를 만들어 주었다. 참고로 아래 코드에서 PATH 값은 자바스크립트 파일을 별도로 분리하다 보니 jstl함수를 사용할 수 없어 path값을 웹브라우저 session storage에 저장시켜두어 외부 자바스크립트 파일에서 꺼내 사용할 수 있도록 하였다.
//서머노트 이미지 ajax 업로드
function sendFile(file, editor) {
var PATH = getContextPath(); //contextPath값
data = new FormData(); //파일 전송을 위한 폼생성
//console.log(file);
//console.log(editor);
data.append("uploadFile", file); //data에 매개변수로 받은 file 값을 넣어준다.
//ajax를 통한 파일 업로드 처리
$.ajax({
data: data,
type: "POST",
url: PATH + "/summernoteImageUpload.do",
enctype: "multipart/form-data",
cache: false,
contentType: false, //multipart/form-data로 처리되는 것과 같음
processData: false, //파일전송시 자동으로 쿼리스트링 형식으로 전송되지 않도록 막는 처리
success: function(data) { //ajax통신이 성공할 경우
//console.log(data);
//에디터에 이미지 출력
$(editor).summernote('editor.insertImage', data.url);
}
});
}
이렇게 하면 화면 구현과 이미지 업로드 함수 구현을 끝났다. 하지만 여기서 끝이 아니다 이제 컨트롤러에서 파일 데이터 값을 받아서 이미지 url값을 json형태로 변환한 후 호출한 곳으로 전달해주어야 한다.
4. Servlet 컨트롤러 파일 업로드 로직 구현
우선 컨트롤러 파일을 하나 생성한다. 그리고 아래와 같이 코드를 작성해준다.
package controller;
import java.io.IOException;
import java.util.Enumeration;
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 org.json.simple.JSONObject;
import com.oreilly.servlet.MultipartRequest;
import util.FileRenamePolicyImpl;
import util.FileUpload;
@WebServlet("/summernoteImageUpload.do")
public class SummernoteImageController extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fileName = "";
try {
//new MultipartRequest(request, "업로드디렉토리", 제한용량, "인코딩", 파일명중복방지처리옵션)
MultipartRequest multi = new MultipartRequest(request, FileUpload.EDITOR_UPLOAD_PATH,
FileUpload.MAX_UPLOAD, "utf-8", new FileRenamePolicyImpl());
Enumeration files = multi.getFileNames();
String file = (String)files.nextElement();
fileName = multi.getFilesystemName(file);
} catch (Exception e) {
e.printStackTrace();
}
//업로드 url 주소
String url = "/editorImage/" + fileName;
//json 데이터로 변환
JSONObject jsonData = new JSONObject(); //json객체 생성
jsonData.put("url", url); // url값을 json데이터에 넣어준다.
response.setContentType("application/json"); //데이터 타입을 json으로 설정하기 위한 셋팅
response.getWriter().print(jsonData.toJSONString()); //호출한 곳으로 json데이터 보내기
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
우선 코드를 살펴보자면 처음에 파일이름을 받을 변수 fileName을 선언해준다.
그 후 cos.jar에서 지원하는 객체인 MultipartRequest를 이용하면 파일을 받을 수 있다. 다만 여기서 파일 관련 내용이기 때문에 try~catch문 안에 작성해준다.
코드라인에 주석문으로도 표기해 두었지만 MultipartRequest 객체를 선언할 때 아래와 같은 구조를 가진다.
MultipartRequest multi = new MultipartRequest(request, FileUpload.EDITOR_UPLOAD_PATH,
FileUpload.MAX_UPLOAD, "utf-8", new FileRenamePoicy());
여기서 중요한 부분은 파일 중복 방지 처리 옵션 부분이다. 본래 new DefaultFileRenamePolicy() 라는 메서드를 사용한다. 해당 메서드를 사용하면 업로드된 파일명을 그대로 가져오게 된다. 다만 같은 파일이 업로드되면 뒤에 (1)이 붙어 중복 파일명을 방지한다.
하지만 난 UUID형태의 파일명을 구현하고 싶었다. 그래서 폭풍 검색 끝에 FIleRenamePoicy를 오버라이딩해서 구현할 수 있다는 걸 알게 되었다.. (무려 3일 동안 검색을...) 그래서 또 하나의 클래스를 만들어서 구현해 보았다.
package util;
import java.io.File;
import java.util.UUID;
import com.oreilly.servlet.multipart.FileRenamePolicy;
public class FileRenamePolicyImpl implements FileRenamePolicy{
@Override
public File rename(File f) {
String name = f.getName(); //업로드 파일명
String ext = ""; //확장자
int dot = name.lastIndexOf("."); //마지막 인덱스 값에 . 이없으면 -1
if(dot != -1) {
ext = name.substring(dot);
} else {
ext = "";
}
String filename = UUID.randomUUID()+ext;
File renameFile = new File(f.getParent(), filename);
return renameFile;
}
}
어떤 블로그에서 참고해서 작성했는데 너무 많은 페이지를 열어서 작업하다보니 어디서 봤는지 까먹었다ㅠ 하지만 구현은 제대로 되었다,. 우선 위 코드의 로직은 우선 업로드된 파일명을 받아와 확장자 값을 구하고, UUID 객체를 사용하여 임의 파일명을 생성한 후, 확장자 값을 더해주고 그 값을 리턴한다.
위와 같이 하면 컨트롤러 단의 작업은 끝났다.
5. 톰캣 server.xml 수정하기
이제 로컬 이미지 경로를 설정하기 위해 server.xml만 수정해주면 된다. 만약 이 부분을 수정 안 하면 로컬 경로를 컨트롤러 단에서 직접 입력해주면 된다고 하는데, 이 부분은 보안 문제 때문에 안된다는 내용도 있어서 직접 server.xml 파일을 수정했다.
이클립스에서 톰캣을 연결하고 나면 아래와 같이 Servers 폴더가 생성된다. 해당 경로에서 server.xml을 클릭해준다.
Source탭에서 코드를 보면 맨 밑에 </Host> 태그 위쪽에 아래 태그를 넣어주면 된다. 그럼 맨 처음 봤던 것처럼 이미지가 업로드되는 걸 확인할 수 있다. 만약 실제 로컬 경로가 틀리면 안뜰 수 있으니 꼼꼼히 확인하고 저장해 주자.
<!-- 서머노트 로컬 파일 경로 설정 -->
<Context docBase="실제로컬경로" path="/editorImage/" reloadable="true" />
마지막으로 정리하자면 전체적인 로직을 설명하자면 아래와 같다.
1) 썸머노트 callback함수와 Ajax통신을 통해 썸머노트 에디터에 업로드된 이미지 파일을 컨트롤러에서 받는다.
2) 재정의한 FileRenamePolicy를 통해 파일명을 UUID로 변경해준다.
3) 파일이 저장될 로컬 경로를 표기해준다 ( 이 부분은 아래 5번을 해주어야 위와 같이 사용할 수 있다. 만약 server.xml 설정을 하지 않는다면 이미지 엑박이 뜰 수 있다.)
4) 이후 url을 JSON 데이터로 변환하여 호출한 곳으로 값을 보내준다.
5) Ajax 통신이 성공적으로 되었다면 해당 url로 썸머노트 에디터에 이미지가 표기된다.
6. 마치며.. (feat. 문제점 체크)
구글링을 통해 이렇게 썸머노트 이미지 업로드에 대해 알아봤다. 처음엔 구현이 되어 좋았으나 구현하고 나니 하나의 문제점이 발견되었다. 우선 이미지 업로드 시 파일 저장은 되지만 별도로 삭제처리가 되지 않는다. 이곳저곳 검색해보니 onMediaDelete 함수로 삭제할 수 있다고 하는데... 아직 해결하지 못했다ㅠ ㅠ
혹시 아시는 분 있다면.. 댓글로 알려주세요! 또한 구글링을 통해 학습하고 구현한 내용이라 틀린 부분이 있거나 수정해야 할 부분이 있어도 댓글로 알려주시면 바로 적용해보겠습니다!